@agimon-ai/browse-tool 0.2.11 → 0.2.13

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/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require(`./streamable-http-CDH-wHjE.cjs`);require(`./playwright-test-BicczY9j.cjs`);let t=require(`@modelcontextprotocol/sdk/server/index.js`),n=require(`@modelcontextprotocol/sdk/types.js`),r=require(`liquidjs`);function i(){let e=()=>void 0;return{debug:e,info:e,warn:e,error:e}}var a=class extends Error{code=`UNKNOWN_TOOL`;recovery=`Use ListTools to see available tools.`;availableTools;constructor(e,t,n){super(`Unknown tool: ${e}. Available tools: ${t.slice(0,5).join(`, `)}${t.length>5?`, ... (${t.length} total)`:``}. Use ListTools to see all available tools.`,n),this.name=`UnknownToolError`,this.availableTools=t}},o=class extends Error{code=`TOOL_EXECUTION_ERROR`;recovery;toolName;constructor(e,t,n){super(`Tool execution failed for '${e}': ${t}`,n),this.name=`ToolExecutionError`,this.toolName=e,this.recovery=n?.recovery??`Check tool inputs and try again.`}};function s(e,t){let n=e instanceof Error?e.message:`Unknown error occurred`,r={error:{code:e instanceof Error&&`code`in e?e.code:`TOOL_EXECUTION_ERROR`,message:n,toolName:t,recovery:e instanceof Error&&`recovery`in e?e.recovery:`Check tool inputs and try again.`}};return{content:[{type:`text`,text:JSON.stringify(r,null,2)}],isError:!0}}function c(r){let c=r?.container??e.r,l=r?.logger??i(),u=new t.Server({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),d=c.getAll(e.C.Tool),f=new Map;for(let e of d){let t=e.getDefinition();f.set(t.name,e)}return l.info(`MCP server initialized`,{toolCount:d.length}),u.setRequestHandler(n.ListToolsRequestSchema,async()=>(l.debug(`ListTools request received`),{tools:d.map(e=>e.getDefinition())})),u.setRequestHandler(n.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params;l.debug(`Tool call received`,{toolName:t,timestamp:new Date().toISOString()});let r=f.get(t);if(!r){let e=Array.from(f.keys());throw l.warn(`Unknown tool requested`,{toolName:t,availableTools:e,timestamp:new Date().toISOString()}),new a(t,e)}try{return await r.execute(n)}catch(e){let n=e instanceof o?e:new o(t,e instanceof Error?e.message:String(e),{cause:e});return l.error(`Tool execution failed`,{toolName:t,error:n.message,code:n.code,timestamp:new Date().toISOString()}),s(n,t)}}),u}var l='Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\n\nGoal: {{ toolGoal }}\nPreferred tool name: {{ preferredToolName }}\nPage context: {{ pageContext }}\n\nRequirements:\n- Produce a `tools.yaml` file with a top-level `tools` array.\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\n- `inputSchema` must be JSON Schema with `type: object`.\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\n- The `run` export runs in Node.js, not inside the page.\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\n- Keep the script self-contained and avoid relative imports.\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\n- Prefer read-only behavior unless the goal explicitly requires mutation.\n\nOutput format:\n- Show the full `tools.yaml` content.\n- Show the full `{{ preferredToolName }}.ts` content.\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\n- Filesystem access is allowed because the script runs in Node.js.\n\nExample `tools.yaml` shape:\n```yaml\ntools:\n - name: get_post\n description: Get the current post from the open feed page\n script: get_post.ts\n suggestionActions: Open the author profile and review whether the post is worth engaging with\n capabilities:\n readOnlyHint: true\n openWorldHint: false\n inputSchema:\n type: object\n properties:\n pageId:\n type: string\n browserId:\n type: string\n selector:\n type: string\n required:\n - pageId\n```\n\nExample script shape:\n```ts\nexport const run = async ({ page, browser, input, logger }) => {\n const traceContext = logger.getTraceContext();\n logger.info("reading post", { attributes: { selector: input.selector ?? "body", browserId: browser.browserId } });\n const selector = input.selector ?? "body";\n const text = await page.evaluate((currentSelector) => {\n return document.querySelector(currentSelector)?.textContent ?? "";\n }, selector);\n return {\n pageUrl: page.url(),\n traceContext,\n text,\n };\n};\n```\n';const u={name:`custom_script_authoring`,description:`Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.`,arguments:[{name:`toolGoal`,description:`What the custom tool should do on the page`,required:!0},{name:`toolName`,description:`Preferred snake_case tool name`,required:!1},{name:`pageContext`,description:`Optional context about the target page or workflow`,required:!1}]},d=new r.Liquid;function f(e){let t=e.toolName?.trim()||`custom_page_tool`,n=e.pageContext?.trim()||`No extra page context provided.`;return[{role:`user`,content:{type:`text`,text:d.parseAndRenderSync(l,{toolGoal:e.toolGoal,preferredToolName:t,pageContext:n})}}]}exports.PLAYWRIGHT_TYPES=e.C,exports.StdioTransportHandler=e.n,exports.StreamableHttpTransportHandler=e.t,exports.ToolExecutionError=o,exports.UnknownToolError=a,exports.container=e.r,exports.createContainer=e.i,exports.createServer=c,exports.customScriptAuthoringPrompt=u,exports.generateCustomScriptAuthoringPrompt=f;
1
+ const e=require(`./streamable-http-O6YYT3Yr.cjs`);require(`./playwright-test-DfpyCIc0.cjs`);let t=require(`@modelcontextprotocol/sdk/server/index.js`),n=require(`@modelcontextprotocol/sdk/types.js`),r=require(`liquidjs`);function i(e){return typeof e==`string`||typeof e==`number`||typeof e==`boolean`?e:JSON.stringify(e)}function a(e){if(!e)return;let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[n]=i(r));return Object.keys(t).length>0?t:void 0}function o(){let t=new e.x({serviceName:`browse-tool-mcp`});return{debug(e,n){t.log(`debug`,e,{attributes:a(n)})},info(e,n){t.log(`info`,e,{attributes:a(n)})},warn(e,n){t.log(`warn`,e,{attributes:a(n)})},error(e,n){t.log(`error`,e,{attributes:a(n)})}}}var s=class extends Error{code=`UNKNOWN_TOOL`;recovery=`Use ListTools to see available tools.`;availableTools;constructor(e,t,n){super(`Unknown tool: ${e}. Available tools: ${t.slice(0,5).join(`, `)}${t.length>5?`, ... (${t.length} total)`:``}. Use ListTools to see all available tools.`,n),this.name=`UnknownToolError`,this.availableTools=t}},c=class extends Error{code=`TOOL_EXECUTION_ERROR`;recovery;toolName;constructor(e,t,n){super(`Tool execution failed for '${e}': ${t}`,n),this.name=`ToolExecutionError`,this.toolName=e,this.recovery=n?.recovery??`Check tool inputs and try again.`}};function l(e,t){let n=e instanceof Error?e.message:`Unknown error occurred`,r={error:{code:e instanceof Error&&`code`in e?e.code:`TOOL_EXECUTION_ERROR`,message:n,toolName:t,recovery:e instanceof Error&&`recovery`in e?e.recovery:`Check tool inputs and try again.`}};return{content:[{type:`text`,text:JSON.stringify(r,null,2)}],isError:!0}}function u(r){let i=r?.container??e.r,a=r?.logger??o(),u=new t.Server({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),d=i.getAll(e.C.Tool),f=new Map;for(let e of d){let t=e.getDefinition();f.set(t.name,e)}return a.info(`MCP server initialized`,{toolCount:d.length}),u.setRequestHandler(n.ListToolsRequestSchema,async()=>(a.debug(`ListTools request received`),{tools:d.map(e=>e.getDefinition())})),u.setRequestHandler(n.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params;a.debug(`Tool call received`,{toolName:t,timestamp:new Date().toISOString()});let r=f.get(t);if(!r){let e=Array.from(f.keys());throw a.warn(`Unknown tool requested`,{toolName:t,availableTools:e,timestamp:new Date().toISOString()}),new s(t,e)}try{return await r.execute(n)}catch(e){let n=e instanceof c?e:new c(t,e instanceof Error?e.message:String(e),{cause:e});return a.error(`Tool execution failed`,{toolName:t,error:n.message,code:n.code,timestamp:new Date().toISOString()}),l(n,t)}}),u}var d='Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\n\nGoal: {{ toolGoal }}\nPreferred tool name: {{ preferredToolName }}\nPage context: {{ pageContext }}\n\nRequirements:\n- Produce a `tools.yaml` file with a top-level `tools` array.\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\n- `inputSchema` must be JSON Schema with `type: object`.\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\n- The `run` export runs in Node.js, not inside the page.\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\n- Keep the script self-contained and avoid relative imports.\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\n- Prefer read-only behavior unless the goal explicitly requires mutation.\n\nOutput format:\n- Show the full `tools.yaml` content.\n- Show the full `{{ preferredToolName }}.ts` content.\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\n- Filesystem access is allowed because the script runs in Node.js.\n\nExample `tools.yaml` shape:\n```yaml\ntools:\n - name: get_post\n description: Get the current post from the open feed page\n script: get_post.ts\n suggestionActions: Open the author profile and review whether the post is worth engaging with\n capabilities:\n readOnlyHint: true\n openWorldHint: false\n inputSchema:\n type: object\n properties:\n pageId:\n type: string\n browserId:\n type: string\n selector:\n type: string\n required:\n - pageId\n```\n\nExample script shape:\n```ts\nexport const run = async ({ page, browser, input, logger }) => {\n const traceContext = logger.getTraceContext();\n logger.info("reading post", { attributes: { selector: input.selector ?? "body", browserId: browser.browserId } });\n const selector = input.selector ?? "body";\n const text = await page.evaluate((currentSelector) => {\n return document.querySelector(currentSelector)?.textContent ?? "";\n }, selector);\n return {\n pageUrl: page.url(),\n traceContext,\n text,\n };\n};\n```\n';const f={name:`custom_script_authoring`,description:`Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.`,arguments:[{name:`toolGoal`,description:`What the custom tool should do on the page`,required:!0},{name:`toolName`,description:`Preferred snake_case tool name`,required:!1},{name:`pageContext`,description:`Optional context about the target page or workflow`,required:!1}]},p=new r.Liquid;function m(e){let t=e.toolName?.trim()||`custom_page_tool`,n=e.pageContext?.trim()||`No extra page context provided.`;return[{role:`user`,content:{type:`text`,text:p.parseAndRenderSync(d,{toolGoal:e.toolGoal,preferredToolName:t,pageContext:n})}}]}exports.PLAYWRIGHT_TYPES=e.C,exports.StdioTransportHandler=e.n,exports.StreamableHttpTransportHandler=e.t,exports.ToolExecutionError=c,exports.UnknownToolError=s,exports.container=e.r,exports.createContainer=e.i,exports.createServer=u,exports.customScriptAuthoringPrompt=f,exports.generateCustomScriptAuthoringPrompt=m;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["defaultContainer","Server","PLAYWRIGHT_TYPES","ListToolsRequestSchema","CallToolRequestSchema","Liquid","promptTemplate"],"sources":["../src/server/index.ts","../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts"],"sourcesContent":["/**\n * MCP Server Setup\n *\n * DESIGN PATTERNS:\n * - Factory pattern for server creation with IoC container\n * - Tool registry pattern for fast lookup\n * - Structured error handling with custom error classes\n *\n * CODING STANDARDS:\n * - Tools are loaded from IoC container on server creation\n * - Proper error handling in all request handlers with custom error classes\n * - Errors include codes, recovery suggestions, and available tools list\n *\n * AVOID:\n * - Hardcoded tool lists (use IoC container)\n * - Missing error handling in handlers\n * - Generic error messages without recovery suggestions\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES, container as defaultContainer } from '../container/index.js';\nimport type { Tool } from '../types/index.js';\n\n// ============================================================================\n// Logger Interface\n// ============================================================================\n\n/** Logger interface for dependency injection */\nexport interface Logger {\n debug(message: string, context?: Record<string, unknown>): void;\n info(message: string, context?: Record<string, unknown>): void;\n warn(message: string, context?: Record<string, unknown>): void;\n error(message: string, context?: Record<string, unknown>): void;\n}\n\n/**\n * Creates a no-op logger for silent operation.\n * @returns A logger implementation that discards all messages\n */\nfunction createNoopLogger(): Logger {\n const noop = (): void => undefined;\n return { debug: noop, info: noop, warn: noop, error: noop };\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\n/**\n * Error thrown when an unknown tool is requested.\n * Provides error code, recovery suggestion, and available tools list.\n */\nexport class UnknownToolError extends Error {\n readonly code = 'UNKNOWN_TOOL';\n readonly recovery = 'Use ListTools to see available tools.';\n readonly availableTools: string[];\n\n constructor(toolName: string, availableTools: string[], options?: ErrorOptions) {\n super(\n `Unknown tool: ${toolName}. Available tools: ${availableTools.slice(0, 5).join(', ')}${availableTools.length > 5 ? `, ... (${availableTools.length} total)` : ''}. Use ListTools to see all available tools.`,\n options,\n );\n this.name = 'UnknownToolError';\n this.availableTools = availableTools;\n }\n}\n\n/**\n * Error thrown when tool execution fails.\n * Provides error code, tool name context, and recovery suggestion.\n */\nexport class ToolExecutionError extends Error {\n readonly code = 'TOOL_EXECUTION_ERROR';\n readonly recovery: string;\n readonly toolName: string;\n\n constructor(toolName: string, message: string, options?: ErrorOptions & { recovery?: string }) {\n super(`Tool execution failed for '${toolName}': ${message}`, options);\n this.name = 'ToolExecutionError';\n this.toolName = toolName;\n this.recovery = options?.recovery ?? 'Check tool inputs and try again.';\n }\n}\n\n// ============================================================================\n// Server Configuration\n// ============================================================================\n\n/**\n * Configuration options for the MCP server.\n */\nexport interface ServerConfig {\n /** Optional IoC container (defaults to the shared container) */\n container?: Container;\n /** Optional logger for debugging and error tracking */\n logger?: Logger;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Creates a structured error response following MCP conventions.\n * Includes error codes, recovery suggestions, and context metadata.\n * @param error - The error that occurred\n * @param toolName - Name of the tool that failed\n * @returns CallToolResult with isError flag and structured error content\n */\nfunction createErrorResponse(error: unknown, toolName: string): CallToolResult {\n const message = error instanceof Error ? error.message : 'Unknown error occurred';\n const code = error instanceof Error && 'code' in error ? (error as { code: string }).code : 'TOOL_EXECUTION_ERROR';\n const recovery =\n error instanceof Error && 'recovery' in error\n ? (error as { recovery: string }).recovery\n : 'Check tool inputs and try again.';\n\n const errorResponse = {\n error: {\n code,\n message,\n toolName,\n recovery,\n },\n };\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(errorResponse, null, 2),\n },\n ],\n isError: true,\n };\n}\n\n// ============================================================================\n// Server Factory\n// ============================================================================\n\n/**\n * Creates a new MCP server instance with tools from the IoC container.\n * @param config - Optional server configuration\n * @returns Configured MCP Server instance\n */\nexport function createServer(config?: ServerConfig): Server {\n const iocContainer = config?.container ?? defaultContainer;\n const logger = config?.logger ?? createNoopLogger();\n\n const server = new Server(\n {\n name: 'browse-tool',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Get all tools from the container\n const tools = iocContainer.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n // Build tool map for fast lookup\n const toolMap = new Map<string, Tool>();\n for (const tool of tools) {\n const def = tool.getDefinition();\n toolMap.set(def.name, tool);\n }\n\n logger.info('MCP server initialized', { toolCount: tools.length });\n\n // List all available tools\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n logger.debug('ListTools request received');\n return {\n tools: tools.map((tool) => tool.getDefinition()),\n };\n });\n\n // Execute tool by name\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n logger.debug('Tool call received', { toolName: name, timestamp: new Date().toISOString() });\n\n const tool = toolMap.get(name);\n\n if (!tool) {\n const availableTools = Array.from(toolMap.keys());\n logger.warn('Unknown tool requested', { toolName: name, availableTools, timestamp: new Date().toISOString() });\n throw new UnknownToolError(name, availableTools);\n }\n\n try {\n return await tool.execute(args);\n } catch (error) {\n // Wrap in ToolExecutionError for consistent error handling\n const toolError =\n error instanceof ToolExecutionError\n ? error\n : new ToolExecutionError(name, error instanceof Error ? error.message : String(error), { cause: error });\n\n logger.error('Tool execution failed', {\n toolName: name,\n error: toolError.message,\n code: toolError.code,\n timestamp: new Date().toISOString(),\n });\n return createErrorResponse(toolError, name);\n }\n });\n\n return server;\n}\n","export default \"Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\\n\\nGoal: {{ toolGoal }}\\nPreferred tool name: {{ preferredToolName }}\\nPage context: {{ pageContext }}\\n\\nRequirements:\\n- Produce a `tools.yaml` file with a top-level `tools` array.\\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\\n- `inputSchema` must be JSON Schema with `type: object`.\\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\\n- The `run` export runs in Node.js, not inside the page.\\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\\n- Keep the script self-contained and avoid relative imports.\\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\\n- Prefer read-only behavior unless the goal explicitly requires mutation.\\n\\nOutput format:\\n- Show the full `tools.yaml` content.\\n- Show the full `{{ preferredToolName }}.ts` content.\\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\\n- Filesystem access is allowed because the script runs in Node.js.\\n\\nExample `tools.yaml` shape:\\n```yaml\\ntools:\\n - name: get_post\\n description: Get the current post from the open feed page\\n script: get_post.ts\\n suggestionActions: Open the author profile and review whether the post is worth engaging with\\n capabilities:\\n readOnlyHint: true\\n openWorldHint: false\\n inputSchema:\\n type: object\\n properties:\\n pageId:\\n type: string\\n browserId:\\n type: string\\n selector:\\n type: string\\n required:\\n - pageId\\n```\\n\\nExample script shape:\\n```ts\\nexport const run = async ({ page, browser, input, logger }) => {\\n const traceContext = logger.getTraceContext();\\n logger.info(\\\"reading post\\\", { attributes: { selector: input.selector ?? \\\"body\\\", browserId: browser.browserId } });\\n const selector = input.selector ?? \\\"body\\\";\\n const text = await page.evaluate((currentSelector) => {\\n return document.querySelector(currentSelector)?.textContent ?? \\\"\\\";\\n }, selector);\\n return {\\n pageUrl: page.url(),\\n traceContext,\\n text,\\n };\\n};\\n```\\n\"","/**\n * CustomScriptAuthoringPrompt\n *\n * Guides an AI assistant to create browse-tool custom tool folders that work\n * with the `mcp-serve --custom-tools <dir>` flow.\n */\nimport { Liquid } from 'liquidjs';\nimport promptTemplate from './instructions/custom-script-authoring.md?raw';\n\nexport const customScriptAuthoringPrompt = {\n name: 'custom_script_authoring',\n description: 'Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.',\n arguments: [\n {\n name: 'toolGoal',\n description: 'What the custom tool should do on the page',\n required: true,\n },\n {\n name: 'toolName',\n description: 'Preferred snake_case tool name',\n required: false,\n },\n {\n name: 'pageContext',\n description: 'Optional context about the target page or workflow',\n required: false,\n },\n ],\n};\n\ninterface GenerateCustomScriptAuthoringPromptArgs {\n toolGoal: string;\n toolName?: string;\n pageContext?: string;\n}\n\nconst liquid = new Liquid();\n\nexport function generateCustomScriptAuthoringPrompt(\n args: GenerateCustomScriptAuthoringPromptArgs,\n): Array<{ role: string; content: { type: string; text: string } }> {\n const preferredToolName = args.toolName?.trim() || 'custom_page_tool';\n const pageContext = args.pageContext?.trim() || 'No extra page context provided.';\n const text = liquid.parseAndRenderSync(promptTemplate, {\n toolGoal: args.toolGoal,\n preferredToolName,\n pageContext,\n });\n\n return [\n {\n role: 'user',\n content: {\n type: 'text',\n text,\n },\n },\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["attributes: Attributes","TelemetryService","defaultContainer","Server","PLAYWRIGHT_TYPES","ListToolsRequestSchema","CallToolRequestSchema","Liquid","promptTemplate"],"sources":["../src/server/index.ts","../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts"],"sourcesContent":["/**\n * MCP Server Setup\n *\n * DESIGN PATTERNS:\n * - Factory pattern for server creation with IoC container\n * - Tool registry pattern for fast lookup\n * - Structured error handling with custom error classes\n *\n * CODING STANDARDS:\n * - Tools are loaded from IoC container on server creation\n * - Proper error handling in all request handlers with custom error classes\n * - Errors include codes, recovery suggestions, and available tools list\n *\n * AVOID:\n * - Hardcoded tool lists (use IoC container)\n * - Missing error handling in handlers\n * - Generic error messages without recovery suggestions\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Attributes, AttributeValue } from '@opentelemetry/api';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES, container as defaultContainer } from '../container/index.js';\nimport { TelemetryService } from '../services/TelemetryService.js';\nimport type { Tool } from '../types/index.js';\n\n// ============================================================================\n// Logger Interface\n// ============================================================================\n\n/** Logger interface for dependency injection */\nexport interface Logger {\n debug(message: string, context?: Record<string, unknown>): void;\n info(message: string, context?: Record<string, unknown>): void;\n warn(message: string, context?: Record<string, unknown>): void;\n error(message: string, context?: Record<string, unknown>): void;\n}\n\nfunction toAttributeValue(value: unknown): AttributeValue {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return value;\n }\n\n return JSON.stringify(value);\n}\n\nfunction toAttributes(context?: Record<string, unknown>): Attributes | undefined {\n if (!context) {\n return undefined;\n }\n\n const attributes: Attributes = {};\n for (const [key, value] of Object.entries(context)) {\n if (value === undefined || value === null) {\n continue;\n }\n\n attributes[key] = toAttributeValue(value);\n }\n\n return Object.keys(attributes).length > 0 ? attributes : undefined;\n}\n\nfunction createTelemetryLogger(): Logger {\n const telemetry = new TelemetryService({\n serviceName: 'browse-tool-mcp',\n });\n\n return {\n debug(message, context) {\n telemetry.log('debug', message, { attributes: toAttributes(context) });\n },\n info(message, context) {\n telemetry.log('info', message, { attributes: toAttributes(context) });\n },\n warn(message, context) {\n telemetry.log('warn', message, { attributes: toAttributes(context) });\n },\n error(message, context) {\n telemetry.log('error', message, { attributes: toAttributes(context) });\n },\n };\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\n/**\n * Error thrown when an unknown tool is requested.\n * Provides error code, recovery suggestion, and available tools list.\n */\nexport class UnknownToolError extends Error {\n readonly code = 'UNKNOWN_TOOL';\n readonly recovery = 'Use ListTools to see available tools.';\n readonly availableTools: string[];\n\n constructor(toolName: string, availableTools: string[], options?: ErrorOptions) {\n super(\n `Unknown tool: ${toolName}. Available tools: ${availableTools.slice(0, 5).join(', ')}${availableTools.length > 5 ? `, ... (${availableTools.length} total)` : ''}. Use ListTools to see all available tools.`,\n options,\n );\n this.name = 'UnknownToolError';\n this.availableTools = availableTools;\n }\n}\n\n/**\n * Error thrown when tool execution fails.\n * Provides error code, tool name context, and recovery suggestion.\n */\nexport class ToolExecutionError extends Error {\n readonly code = 'TOOL_EXECUTION_ERROR';\n readonly recovery: string;\n readonly toolName: string;\n\n constructor(toolName: string, message: string, options?: ErrorOptions & { recovery?: string }) {\n super(`Tool execution failed for '${toolName}': ${message}`, options);\n this.name = 'ToolExecutionError';\n this.toolName = toolName;\n this.recovery = options?.recovery ?? 'Check tool inputs and try again.';\n }\n}\n\n// ============================================================================\n// Server Configuration\n// ============================================================================\n\n/**\n * Configuration options for the MCP server.\n */\nexport interface ServerConfig {\n /** Optional IoC container (defaults to the shared container) */\n container?: Container;\n /** Optional logger for debugging and error tracking */\n logger?: Logger;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Creates a structured error response following MCP conventions.\n * Includes error codes, recovery suggestions, and context metadata.\n * @param error - The error that occurred\n * @param toolName - Name of the tool that failed\n * @returns CallToolResult with isError flag and structured error content\n */\nfunction createErrorResponse(error: unknown, toolName: string): CallToolResult {\n const message = error instanceof Error ? error.message : 'Unknown error occurred';\n const code = error instanceof Error && 'code' in error ? (error as { code: string }).code : 'TOOL_EXECUTION_ERROR';\n const recovery =\n error instanceof Error && 'recovery' in error\n ? (error as { recovery: string }).recovery\n : 'Check tool inputs and try again.';\n\n const errorResponse = {\n error: {\n code,\n message,\n toolName,\n recovery,\n },\n };\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(errorResponse, null, 2),\n },\n ],\n isError: true,\n };\n}\n\n// ============================================================================\n// Server Factory\n// ============================================================================\n\n/**\n * Creates a new MCP server instance with tools from the IoC container.\n * @param config - Optional server configuration\n * @returns Configured MCP Server instance\n */\nexport function createServer(config?: ServerConfig): Server {\n const iocContainer = config?.container ?? defaultContainer;\n const logger = config?.logger ?? createTelemetryLogger();\n\n const server = new Server(\n {\n name: 'browse-tool',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Get all tools from the container\n const tools = iocContainer.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n // Build tool map for fast lookup\n const toolMap = new Map<string, Tool>();\n for (const tool of tools) {\n const def = tool.getDefinition();\n toolMap.set(def.name, tool);\n }\n\n logger.info('MCP server initialized', { toolCount: tools.length });\n\n // List all available tools\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n logger.debug('ListTools request received');\n return {\n tools: tools.map((tool) => tool.getDefinition()),\n };\n });\n\n // Execute tool by name\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n logger.debug('Tool call received', { toolName: name, timestamp: new Date().toISOString() });\n\n const tool = toolMap.get(name);\n\n if (!tool) {\n const availableTools = Array.from(toolMap.keys());\n logger.warn('Unknown tool requested', { toolName: name, availableTools, timestamp: new Date().toISOString() });\n throw new UnknownToolError(name, availableTools);\n }\n\n try {\n return await tool.execute(args);\n } catch (error) {\n // Wrap in ToolExecutionError for consistent error handling\n const toolError =\n error instanceof ToolExecutionError\n ? error\n : new ToolExecutionError(name, error instanceof Error ? error.message : String(error), { cause: error });\n\n logger.error('Tool execution failed', {\n toolName: name,\n error: toolError.message,\n code: toolError.code,\n timestamp: new Date().toISOString(),\n });\n return createErrorResponse(toolError, name);\n }\n });\n\n return server;\n}\n","export default \"Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\\n\\nGoal: {{ toolGoal }}\\nPreferred tool name: {{ preferredToolName }}\\nPage context: {{ pageContext }}\\n\\nRequirements:\\n- Produce a `tools.yaml` file with a top-level `tools` array.\\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\\n- `inputSchema` must be JSON Schema with `type: object`.\\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\\n- The `run` export runs in Node.js, not inside the page.\\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\\n- Keep the script self-contained and avoid relative imports.\\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\\n- Prefer read-only behavior unless the goal explicitly requires mutation.\\n\\nOutput format:\\n- Show the full `tools.yaml` content.\\n- Show the full `{{ preferredToolName }}.ts` content.\\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\\n- Filesystem access is allowed because the script runs in Node.js.\\n\\nExample `tools.yaml` shape:\\n```yaml\\ntools:\\n - name: get_post\\n description: Get the current post from the open feed page\\n script: get_post.ts\\n suggestionActions: Open the author profile and review whether the post is worth engaging with\\n capabilities:\\n readOnlyHint: true\\n openWorldHint: false\\n inputSchema:\\n type: object\\n properties:\\n pageId:\\n type: string\\n browserId:\\n type: string\\n selector:\\n type: string\\n required:\\n - pageId\\n```\\n\\nExample script shape:\\n```ts\\nexport const run = async ({ page, browser, input, logger }) => {\\n const traceContext = logger.getTraceContext();\\n logger.info(\\\"reading post\\\", { attributes: { selector: input.selector ?? \\\"body\\\", browserId: browser.browserId } });\\n const selector = input.selector ?? \\\"body\\\";\\n const text = await page.evaluate((currentSelector) => {\\n return document.querySelector(currentSelector)?.textContent ?? \\\"\\\";\\n }, selector);\\n return {\\n pageUrl: page.url(),\\n traceContext,\\n text,\\n };\\n};\\n```\\n\"","/**\n * CustomScriptAuthoringPrompt\n *\n * Guides an AI assistant to create browse-tool custom tool folders that work\n * with the `mcp-serve --custom-tools <dir>` flow.\n */\nimport { Liquid } from 'liquidjs';\nimport promptTemplate from './instructions/custom-script-authoring.md?raw';\n\nexport const customScriptAuthoringPrompt = {\n name: 'custom_script_authoring',\n description: 'Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.',\n arguments: [\n {\n name: 'toolGoal',\n description: 'What the custom tool should do on the page',\n required: true,\n },\n {\n name: 'toolName',\n description: 'Preferred snake_case tool name',\n required: false,\n },\n {\n name: 'pageContext',\n description: 'Optional context about the target page or workflow',\n required: false,\n },\n ],\n};\n\ninterface GenerateCustomScriptAuthoringPromptArgs {\n toolGoal: string;\n toolName?: string;\n pageContext?: string;\n}\n\nconst liquid = new Liquid();\n\nexport function generateCustomScriptAuthoringPrompt(\n args: GenerateCustomScriptAuthoringPromptArgs,\n): Array<{ role: string; content: { type: string; text: string } }> {\n const preferredToolName = args.toolName?.trim() || 'custom_page_tool';\n const pageContext = args.pageContext?.trim() || 'No extra page context provided.';\n const text = liquid.parseAndRenderSync(promptTemplate, {\n toolGoal: args.toolGoal,\n preferredToolName,\n pageContext,\n });\n\n return [\n {\n role: 'user',\n content: {\n type: 'text',\n text,\n },\n },\n ];\n}\n"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{C as e,i as t,n,r,t as i}from"./streamable-http-B3rh1cLd.mjs";import"./playwright-test-CjR5XFNZ.mjs";import{Server as a}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as o,ListToolsRequestSchema as s}from"@modelcontextprotocol/sdk/types.js";import{Liquid as c}from"liquidjs";function l(){let e=()=>void 0;return{debug:e,info:e,warn:e,error:e}}var u=class extends Error{code=`UNKNOWN_TOOL`;recovery=`Use ListTools to see available tools.`;availableTools;constructor(e,t,n){super(`Unknown tool: ${e}. Available tools: ${t.slice(0,5).join(`, `)}${t.length>5?`, ... (${t.length} total)`:``}. Use ListTools to see all available tools.`,n),this.name=`UnknownToolError`,this.availableTools=t}},d=class extends Error{code=`TOOL_EXECUTION_ERROR`;recovery;toolName;constructor(e,t,n){super(`Tool execution failed for '${e}': ${t}`,n),this.name=`ToolExecutionError`,this.toolName=e,this.recovery=n?.recovery??`Check tool inputs and try again.`}};function f(e,t){let n=e instanceof Error?e.message:`Unknown error occurred`,r={error:{code:e instanceof Error&&`code`in e?e.code:`TOOL_EXECUTION_ERROR`,message:n,toolName:t,recovery:e instanceof Error&&`recovery`in e?e.recovery:`Check tool inputs and try again.`}};return{content:[{type:`text`,text:JSON.stringify(r,null,2)}],isError:!0}}function p(t){let n=t?.container??r,i=t?.logger??l(),c=new a({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),p=n.getAll(e.Tool),m=new Map;for(let e of p){let t=e.getDefinition();m.set(t.name,e)}return i.info(`MCP server initialized`,{toolCount:p.length}),c.setRequestHandler(s,async()=>(i.debug(`ListTools request received`),{tools:p.map(e=>e.getDefinition())})),c.setRequestHandler(o,async e=>{let{name:t,arguments:n}=e.params;i.debug(`Tool call received`,{toolName:t,timestamp:new Date().toISOString()});let r=m.get(t);if(!r){let e=Array.from(m.keys());throw i.warn(`Unknown tool requested`,{toolName:t,availableTools:e,timestamp:new Date().toISOString()}),new u(t,e)}try{return await r.execute(n)}catch(e){let n=e instanceof d?e:new d(t,e instanceof Error?e.message:String(e),{cause:e});return i.error(`Tool execution failed`,{toolName:t,error:n.message,code:n.code,timestamp:new Date().toISOString()}),f(n,t)}}),c}var m='Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\n\nGoal: {{ toolGoal }}\nPreferred tool name: {{ preferredToolName }}\nPage context: {{ pageContext }}\n\nRequirements:\n- Produce a `tools.yaml` file with a top-level `tools` array.\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\n- `inputSchema` must be JSON Schema with `type: object`.\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\n- The `run` export runs in Node.js, not inside the page.\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\n- Keep the script self-contained and avoid relative imports.\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\n- Prefer read-only behavior unless the goal explicitly requires mutation.\n\nOutput format:\n- Show the full `tools.yaml` content.\n- Show the full `{{ preferredToolName }}.ts` content.\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\n- Filesystem access is allowed because the script runs in Node.js.\n\nExample `tools.yaml` shape:\n```yaml\ntools:\n - name: get_post\n description: Get the current post from the open feed page\n script: get_post.ts\n suggestionActions: Open the author profile and review whether the post is worth engaging with\n capabilities:\n readOnlyHint: true\n openWorldHint: false\n inputSchema:\n type: object\n properties:\n pageId:\n type: string\n browserId:\n type: string\n selector:\n type: string\n required:\n - pageId\n```\n\nExample script shape:\n```ts\nexport const run = async ({ page, browser, input, logger }) => {\n const traceContext = logger.getTraceContext();\n logger.info("reading post", { attributes: { selector: input.selector ?? "body", browserId: browser.browserId } });\n const selector = input.selector ?? "body";\n const text = await page.evaluate((currentSelector) => {\n return document.querySelector(currentSelector)?.textContent ?? "";\n }, selector);\n return {\n pageUrl: page.url(),\n traceContext,\n text,\n };\n};\n```\n';const h={name:`custom_script_authoring`,description:`Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.`,arguments:[{name:`toolGoal`,description:`What the custom tool should do on the page`,required:!0},{name:`toolName`,description:`Preferred snake_case tool name`,required:!1},{name:`pageContext`,description:`Optional context about the target page or workflow`,required:!1}]},g=new c;function _(e){let t=e.toolName?.trim()||`custom_page_tool`,n=e.pageContext?.trim()||`No extra page context provided.`;return[{role:`user`,content:{type:`text`,text:g.parseAndRenderSync(m,{toolGoal:e.toolGoal,preferredToolName:t,pageContext:n})}}]}export{e as PLAYWRIGHT_TYPES,n as StdioTransportHandler,i as StreamableHttpTransportHandler,d as ToolExecutionError,u as UnknownToolError,r as container,t as createContainer,p as createServer,h as customScriptAuthoringPrompt,_ as generateCustomScriptAuthoringPrompt};
1
+ import{C as e,i as t,n,r,t as i,x as a}from"./streamable-http-_UIJOzAB.mjs";import"./playwright-test-yNsP599T.mjs";import{Server as o}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as s,ListToolsRequestSchema as c}from"@modelcontextprotocol/sdk/types.js";import{Liquid as l}from"liquidjs";function u(e){return typeof e==`string`||typeof e==`number`||typeof e==`boolean`?e:JSON.stringify(e)}function d(e){if(!e)return;let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[n]=u(r));return Object.keys(t).length>0?t:void 0}function f(){let e=new a({serviceName:`browse-tool-mcp`});return{debug(t,n){e.log(`debug`,t,{attributes:d(n)})},info(t,n){e.log(`info`,t,{attributes:d(n)})},warn(t,n){e.log(`warn`,t,{attributes:d(n)})},error(t,n){e.log(`error`,t,{attributes:d(n)})}}}var p=class extends Error{code=`UNKNOWN_TOOL`;recovery=`Use ListTools to see available tools.`;availableTools;constructor(e,t,n){super(`Unknown tool: ${e}. Available tools: ${t.slice(0,5).join(`, `)}${t.length>5?`, ... (${t.length} total)`:``}. Use ListTools to see all available tools.`,n),this.name=`UnknownToolError`,this.availableTools=t}},m=class extends Error{code=`TOOL_EXECUTION_ERROR`;recovery;toolName;constructor(e,t,n){super(`Tool execution failed for '${e}': ${t}`,n),this.name=`ToolExecutionError`,this.toolName=e,this.recovery=n?.recovery??`Check tool inputs and try again.`}};function h(e,t){let n=e instanceof Error?e.message:`Unknown error occurred`,r={error:{code:e instanceof Error&&`code`in e?e.code:`TOOL_EXECUTION_ERROR`,message:n,toolName:t,recovery:e instanceof Error&&`recovery`in e?e.recovery:`Check tool inputs and try again.`}};return{content:[{type:`text`,text:JSON.stringify(r,null,2)}],isError:!0}}function g(t){let n=t?.container??r,i=t?.logger??f(),a=new o({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),l=n.getAll(e.Tool),u=new Map;for(let e of l){let t=e.getDefinition();u.set(t.name,e)}return i.info(`MCP server initialized`,{toolCount:l.length}),a.setRequestHandler(c,async()=>(i.debug(`ListTools request received`),{tools:l.map(e=>e.getDefinition())})),a.setRequestHandler(s,async e=>{let{name:t,arguments:n}=e.params;i.debug(`Tool call received`,{toolName:t,timestamp:new Date().toISOString()});let r=u.get(t);if(!r){let e=Array.from(u.keys());throw i.warn(`Unknown tool requested`,{toolName:t,availableTools:e,timestamp:new Date().toISOString()}),new p(t,e)}try{return await r.execute(n)}catch(e){let n=e instanceof m?e:new m(t,e instanceof Error?e.message:String(e),{cause:e});return i.error(`Tool execution failed`,{toolName:t,error:n.message,code:n.code,timestamp:new Date().toISOString()}),h(n,t)}}),a}var _='Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\n\nGoal: {{ toolGoal }}\nPreferred tool name: {{ preferredToolName }}\nPage context: {{ pageContext }}\n\nRequirements:\n- Produce a `tools.yaml` file with a top-level `tools` array.\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\n- `inputSchema` must be JSON Schema with `type: object`.\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\n- The `run` export runs in Node.js, not inside the page.\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\n- Keep the script self-contained and avoid relative imports.\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\n- Prefer read-only behavior unless the goal explicitly requires mutation.\n\nOutput format:\n- Show the full `tools.yaml` content.\n- Show the full `{{ preferredToolName }}.ts` content.\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\n- Filesystem access is allowed because the script runs in Node.js.\n\nExample `tools.yaml` shape:\n```yaml\ntools:\n - name: get_post\n description: Get the current post from the open feed page\n script: get_post.ts\n suggestionActions: Open the author profile and review whether the post is worth engaging with\n capabilities:\n readOnlyHint: true\n openWorldHint: false\n inputSchema:\n type: object\n properties:\n pageId:\n type: string\n browserId:\n type: string\n selector:\n type: string\n required:\n - pageId\n```\n\nExample script shape:\n```ts\nexport const run = async ({ page, browser, input, logger }) => {\n const traceContext = logger.getTraceContext();\n logger.info("reading post", { attributes: { selector: input.selector ?? "body", browserId: browser.browserId } });\n const selector = input.selector ?? "body";\n const text = await page.evaluate((currentSelector) => {\n return document.querySelector(currentSelector)?.textContent ?? "";\n }, selector);\n return {\n pageUrl: page.url(),\n traceContext,\n text,\n };\n};\n```\n';const v={name:`custom_script_authoring`,description:`Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.`,arguments:[{name:`toolGoal`,description:`What the custom tool should do on the page`,required:!0},{name:`toolName`,description:`Preferred snake_case tool name`,required:!1},{name:`pageContext`,description:`Optional context about the target page or workflow`,required:!1}]},y=new l;function b(e){let t=e.toolName?.trim()||`custom_page_tool`,n=e.pageContext?.trim()||`No extra page context provided.`;return[{role:`user`,content:{type:`text`,text:y.parseAndRenderSync(_,{toolGoal:e.toolGoal,preferredToolName:t,pageContext:n})}}]}export{e as PLAYWRIGHT_TYPES,n as StdioTransportHandler,i as StreamableHttpTransportHandler,m as ToolExecutionError,p as UnknownToolError,r as container,t as createContainer,g as createServer,v as customScriptAuthoringPrompt,b as generateCustomScriptAuthoringPrompt};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["defaultContainer","promptTemplate"],"sources":["../src/server/index.ts","../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts"],"sourcesContent":["/**\n * MCP Server Setup\n *\n * DESIGN PATTERNS:\n * - Factory pattern for server creation with IoC container\n * - Tool registry pattern for fast lookup\n * - Structured error handling with custom error classes\n *\n * CODING STANDARDS:\n * - Tools are loaded from IoC container on server creation\n * - Proper error handling in all request handlers with custom error classes\n * - Errors include codes, recovery suggestions, and available tools list\n *\n * AVOID:\n * - Hardcoded tool lists (use IoC container)\n * - Missing error handling in handlers\n * - Generic error messages without recovery suggestions\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES, container as defaultContainer } from '../container/index.js';\nimport type { Tool } from '../types/index.js';\n\n// ============================================================================\n// Logger Interface\n// ============================================================================\n\n/** Logger interface for dependency injection */\nexport interface Logger {\n debug(message: string, context?: Record<string, unknown>): void;\n info(message: string, context?: Record<string, unknown>): void;\n warn(message: string, context?: Record<string, unknown>): void;\n error(message: string, context?: Record<string, unknown>): void;\n}\n\n/**\n * Creates a no-op logger for silent operation.\n * @returns A logger implementation that discards all messages\n */\nfunction createNoopLogger(): Logger {\n const noop = (): void => undefined;\n return { debug: noop, info: noop, warn: noop, error: noop };\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\n/**\n * Error thrown when an unknown tool is requested.\n * Provides error code, recovery suggestion, and available tools list.\n */\nexport class UnknownToolError extends Error {\n readonly code = 'UNKNOWN_TOOL';\n readonly recovery = 'Use ListTools to see available tools.';\n readonly availableTools: string[];\n\n constructor(toolName: string, availableTools: string[], options?: ErrorOptions) {\n super(\n `Unknown tool: ${toolName}. Available tools: ${availableTools.slice(0, 5).join(', ')}${availableTools.length > 5 ? `, ... (${availableTools.length} total)` : ''}. Use ListTools to see all available tools.`,\n options,\n );\n this.name = 'UnknownToolError';\n this.availableTools = availableTools;\n }\n}\n\n/**\n * Error thrown when tool execution fails.\n * Provides error code, tool name context, and recovery suggestion.\n */\nexport class ToolExecutionError extends Error {\n readonly code = 'TOOL_EXECUTION_ERROR';\n readonly recovery: string;\n readonly toolName: string;\n\n constructor(toolName: string, message: string, options?: ErrorOptions & { recovery?: string }) {\n super(`Tool execution failed for '${toolName}': ${message}`, options);\n this.name = 'ToolExecutionError';\n this.toolName = toolName;\n this.recovery = options?.recovery ?? 'Check tool inputs and try again.';\n }\n}\n\n// ============================================================================\n// Server Configuration\n// ============================================================================\n\n/**\n * Configuration options for the MCP server.\n */\nexport interface ServerConfig {\n /** Optional IoC container (defaults to the shared container) */\n container?: Container;\n /** Optional logger for debugging and error tracking */\n logger?: Logger;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Creates a structured error response following MCP conventions.\n * Includes error codes, recovery suggestions, and context metadata.\n * @param error - The error that occurred\n * @param toolName - Name of the tool that failed\n * @returns CallToolResult with isError flag and structured error content\n */\nfunction createErrorResponse(error: unknown, toolName: string): CallToolResult {\n const message = error instanceof Error ? error.message : 'Unknown error occurred';\n const code = error instanceof Error && 'code' in error ? (error as { code: string }).code : 'TOOL_EXECUTION_ERROR';\n const recovery =\n error instanceof Error && 'recovery' in error\n ? (error as { recovery: string }).recovery\n : 'Check tool inputs and try again.';\n\n const errorResponse = {\n error: {\n code,\n message,\n toolName,\n recovery,\n },\n };\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(errorResponse, null, 2),\n },\n ],\n isError: true,\n };\n}\n\n// ============================================================================\n// Server Factory\n// ============================================================================\n\n/**\n * Creates a new MCP server instance with tools from the IoC container.\n * @param config - Optional server configuration\n * @returns Configured MCP Server instance\n */\nexport function createServer(config?: ServerConfig): Server {\n const iocContainer = config?.container ?? defaultContainer;\n const logger = config?.logger ?? createNoopLogger();\n\n const server = new Server(\n {\n name: 'browse-tool',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Get all tools from the container\n const tools = iocContainer.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n // Build tool map for fast lookup\n const toolMap = new Map<string, Tool>();\n for (const tool of tools) {\n const def = tool.getDefinition();\n toolMap.set(def.name, tool);\n }\n\n logger.info('MCP server initialized', { toolCount: tools.length });\n\n // List all available tools\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n logger.debug('ListTools request received');\n return {\n tools: tools.map((tool) => tool.getDefinition()),\n };\n });\n\n // Execute tool by name\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n logger.debug('Tool call received', { toolName: name, timestamp: new Date().toISOString() });\n\n const tool = toolMap.get(name);\n\n if (!tool) {\n const availableTools = Array.from(toolMap.keys());\n logger.warn('Unknown tool requested', { toolName: name, availableTools, timestamp: new Date().toISOString() });\n throw new UnknownToolError(name, availableTools);\n }\n\n try {\n return await tool.execute(args);\n } catch (error) {\n // Wrap in ToolExecutionError for consistent error handling\n const toolError =\n error instanceof ToolExecutionError\n ? error\n : new ToolExecutionError(name, error instanceof Error ? error.message : String(error), { cause: error });\n\n logger.error('Tool execution failed', {\n toolName: name,\n error: toolError.message,\n code: toolError.code,\n timestamp: new Date().toISOString(),\n });\n return createErrorResponse(toolError, name);\n }\n });\n\n return server;\n}\n","export default \"Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\\n\\nGoal: {{ toolGoal }}\\nPreferred tool name: {{ preferredToolName }}\\nPage context: {{ pageContext }}\\n\\nRequirements:\\n- Produce a `tools.yaml` file with a top-level `tools` array.\\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\\n- `inputSchema` must be JSON Schema with `type: object`.\\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\\n- The `run` export runs in Node.js, not inside the page.\\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\\n- Keep the script self-contained and avoid relative imports.\\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\\n- Prefer read-only behavior unless the goal explicitly requires mutation.\\n\\nOutput format:\\n- Show the full `tools.yaml` content.\\n- Show the full `{{ preferredToolName }}.ts` content.\\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\\n- Filesystem access is allowed because the script runs in Node.js.\\n\\nExample `tools.yaml` shape:\\n```yaml\\ntools:\\n - name: get_post\\n description: Get the current post from the open feed page\\n script: get_post.ts\\n suggestionActions: Open the author profile and review whether the post is worth engaging with\\n capabilities:\\n readOnlyHint: true\\n openWorldHint: false\\n inputSchema:\\n type: object\\n properties:\\n pageId:\\n type: string\\n browserId:\\n type: string\\n selector:\\n type: string\\n required:\\n - pageId\\n```\\n\\nExample script shape:\\n```ts\\nexport const run = async ({ page, browser, input, logger }) => {\\n const traceContext = logger.getTraceContext();\\n logger.info(\\\"reading post\\\", { attributes: { selector: input.selector ?? \\\"body\\\", browserId: browser.browserId } });\\n const selector = input.selector ?? \\\"body\\\";\\n const text = await page.evaluate((currentSelector) => {\\n return document.querySelector(currentSelector)?.textContent ?? \\\"\\\";\\n }, selector);\\n return {\\n pageUrl: page.url(),\\n traceContext,\\n text,\\n };\\n};\\n```\\n\"","/**\n * CustomScriptAuthoringPrompt\n *\n * Guides an AI assistant to create browse-tool custom tool folders that work\n * with the `mcp-serve --custom-tools <dir>` flow.\n */\nimport { Liquid } from 'liquidjs';\nimport promptTemplate from './instructions/custom-script-authoring.md?raw';\n\nexport const customScriptAuthoringPrompt = {\n name: 'custom_script_authoring',\n description: 'Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.',\n arguments: [\n {\n name: 'toolGoal',\n description: 'What the custom tool should do on the page',\n required: true,\n },\n {\n name: 'toolName',\n description: 'Preferred snake_case tool name',\n required: false,\n },\n {\n name: 'pageContext',\n description: 'Optional context about the target page or workflow',\n required: false,\n },\n ],\n};\n\ninterface GenerateCustomScriptAuthoringPromptArgs {\n toolGoal: string;\n toolName?: string;\n pageContext?: string;\n}\n\nconst liquid = new Liquid();\n\nexport function generateCustomScriptAuthoringPrompt(\n args: GenerateCustomScriptAuthoringPromptArgs,\n): Array<{ role: string; content: { type: string; text: string } }> {\n const preferredToolName = args.toolName?.trim() || 'custom_page_tool';\n const pageContext = args.pageContext?.trim() || 'No extra page context provided.';\n const text = liquid.parseAndRenderSync(promptTemplate, {\n toolGoal: args.toolGoal,\n preferredToolName,\n pageContext,\n });\n\n return [\n {\n role: 'user',\n content: {\n type: 'text',\n text,\n },\n },\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["attributes: Attributes","defaultContainer","promptTemplate"],"sources":["../src/server/index.ts","../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts"],"sourcesContent":["/**\n * MCP Server Setup\n *\n * DESIGN PATTERNS:\n * - Factory pattern for server creation with IoC container\n * - Tool registry pattern for fast lookup\n * - Structured error handling with custom error classes\n *\n * CODING STANDARDS:\n * - Tools are loaded from IoC container on server creation\n * - Proper error handling in all request handlers with custom error classes\n * - Errors include codes, recovery suggestions, and available tools list\n *\n * AVOID:\n * - Hardcoded tool lists (use IoC container)\n * - Missing error handling in handlers\n * - Generic error messages without recovery suggestions\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Attributes, AttributeValue } from '@opentelemetry/api';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES, container as defaultContainer } from '../container/index.js';\nimport { TelemetryService } from '../services/TelemetryService.js';\nimport type { Tool } from '../types/index.js';\n\n// ============================================================================\n// Logger Interface\n// ============================================================================\n\n/** Logger interface for dependency injection */\nexport interface Logger {\n debug(message: string, context?: Record<string, unknown>): void;\n info(message: string, context?: Record<string, unknown>): void;\n warn(message: string, context?: Record<string, unknown>): void;\n error(message: string, context?: Record<string, unknown>): void;\n}\n\nfunction toAttributeValue(value: unknown): AttributeValue {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return value;\n }\n\n return JSON.stringify(value);\n}\n\nfunction toAttributes(context?: Record<string, unknown>): Attributes | undefined {\n if (!context) {\n return undefined;\n }\n\n const attributes: Attributes = {};\n for (const [key, value] of Object.entries(context)) {\n if (value === undefined || value === null) {\n continue;\n }\n\n attributes[key] = toAttributeValue(value);\n }\n\n return Object.keys(attributes).length > 0 ? attributes : undefined;\n}\n\nfunction createTelemetryLogger(): Logger {\n const telemetry = new TelemetryService({\n serviceName: 'browse-tool-mcp',\n });\n\n return {\n debug(message, context) {\n telemetry.log('debug', message, { attributes: toAttributes(context) });\n },\n info(message, context) {\n telemetry.log('info', message, { attributes: toAttributes(context) });\n },\n warn(message, context) {\n telemetry.log('warn', message, { attributes: toAttributes(context) });\n },\n error(message, context) {\n telemetry.log('error', message, { attributes: toAttributes(context) });\n },\n };\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\n/**\n * Error thrown when an unknown tool is requested.\n * Provides error code, recovery suggestion, and available tools list.\n */\nexport class UnknownToolError extends Error {\n readonly code = 'UNKNOWN_TOOL';\n readonly recovery = 'Use ListTools to see available tools.';\n readonly availableTools: string[];\n\n constructor(toolName: string, availableTools: string[], options?: ErrorOptions) {\n super(\n `Unknown tool: ${toolName}. Available tools: ${availableTools.slice(0, 5).join(', ')}${availableTools.length > 5 ? `, ... (${availableTools.length} total)` : ''}. Use ListTools to see all available tools.`,\n options,\n );\n this.name = 'UnknownToolError';\n this.availableTools = availableTools;\n }\n}\n\n/**\n * Error thrown when tool execution fails.\n * Provides error code, tool name context, and recovery suggestion.\n */\nexport class ToolExecutionError extends Error {\n readonly code = 'TOOL_EXECUTION_ERROR';\n readonly recovery: string;\n readonly toolName: string;\n\n constructor(toolName: string, message: string, options?: ErrorOptions & { recovery?: string }) {\n super(`Tool execution failed for '${toolName}': ${message}`, options);\n this.name = 'ToolExecutionError';\n this.toolName = toolName;\n this.recovery = options?.recovery ?? 'Check tool inputs and try again.';\n }\n}\n\n// ============================================================================\n// Server Configuration\n// ============================================================================\n\n/**\n * Configuration options for the MCP server.\n */\nexport interface ServerConfig {\n /** Optional IoC container (defaults to the shared container) */\n container?: Container;\n /** Optional logger for debugging and error tracking */\n logger?: Logger;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Creates a structured error response following MCP conventions.\n * Includes error codes, recovery suggestions, and context metadata.\n * @param error - The error that occurred\n * @param toolName - Name of the tool that failed\n * @returns CallToolResult with isError flag and structured error content\n */\nfunction createErrorResponse(error: unknown, toolName: string): CallToolResult {\n const message = error instanceof Error ? error.message : 'Unknown error occurred';\n const code = error instanceof Error && 'code' in error ? (error as { code: string }).code : 'TOOL_EXECUTION_ERROR';\n const recovery =\n error instanceof Error && 'recovery' in error\n ? (error as { recovery: string }).recovery\n : 'Check tool inputs and try again.';\n\n const errorResponse = {\n error: {\n code,\n message,\n toolName,\n recovery,\n },\n };\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(errorResponse, null, 2),\n },\n ],\n isError: true,\n };\n}\n\n// ============================================================================\n// Server Factory\n// ============================================================================\n\n/**\n * Creates a new MCP server instance with tools from the IoC container.\n * @param config - Optional server configuration\n * @returns Configured MCP Server instance\n */\nexport function createServer(config?: ServerConfig): Server {\n const iocContainer = config?.container ?? defaultContainer;\n const logger = config?.logger ?? createTelemetryLogger();\n\n const server = new Server(\n {\n name: 'browse-tool',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Get all tools from the container\n const tools = iocContainer.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n // Build tool map for fast lookup\n const toolMap = new Map<string, Tool>();\n for (const tool of tools) {\n const def = tool.getDefinition();\n toolMap.set(def.name, tool);\n }\n\n logger.info('MCP server initialized', { toolCount: tools.length });\n\n // List all available tools\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n logger.debug('ListTools request received');\n return {\n tools: tools.map((tool) => tool.getDefinition()),\n };\n });\n\n // Execute tool by name\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n logger.debug('Tool call received', { toolName: name, timestamp: new Date().toISOString() });\n\n const tool = toolMap.get(name);\n\n if (!tool) {\n const availableTools = Array.from(toolMap.keys());\n logger.warn('Unknown tool requested', { toolName: name, availableTools, timestamp: new Date().toISOString() });\n throw new UnknownToolError(name, availableTools);\n }\n\n try {\n return await tool.execute(args);\n } catch (error) {\n // Wrap in ToolExecutionError for consistent error handling\n const toolError =\n error instanceof ToolExecutionError\n ? error\n : new ToolExecutionError(name, error instanceof Error ? error.message : String(error), { cause: error });\n\n logger.error('Tool execution failed', {\n toolName: name,\n error: toolError.message,\n code: toolError.code,\n timestamp: new Date().toISOString(),\n });\n return createErrorResponse(toolError, name);\n }\n });\n\n return server;\n}\n","export default \"Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\\n\\nGoal: {{ toolGoal }}\\nPreferred tool name: {{ preferredToolName }}\\nPage context: {{ pageContext }}\\n\\nRequirements:\\n- Produce a `tools.yaml` file with a top-level `tools` array.\\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\\n- `inputSchema` must be JSON Schema with `type: object`.\\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\\n- The `run` export runs in Node.js, not inside the page.\\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\\n- Keep the script self-contained and avoid relative imports.\\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\\n- Prefer read-only behavior unless the goal explicitly requires mutation.\\n\\nOutput format:\\n- Show the full `tools.yaml` content.\\n- Show the full `{{ preferredToolName }}.ts` content.\\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\\n- Filesystem access is allowed because the script runs in Node.js.\\n\\nExample `tools.yaml` shape:\\n```yaml\\ntools:\\n - name: get_post\\n description: Get the current post from the open feed page\\n script: get_post.ts\\n suggestionActions: Open the author profile and review whether the post is worth engaging with\\n capabilities:\\n readOnlyHint: true\\n openWorldHint: false\\n inputSchema:\\n type: object\\n properties:\\n pageId:\\n type: string\\n browserId:\\n type: string\\n selector:\\n type: string\\n required:\\n - pageId\\n```\\n\\nExample script shape:\\n```ts\\nexport const run = async ({ page, browser, input, logger }) => {\\n const traceContext = logger.getTraceContext();\\n logger.info(\\\"reading post\\\", { attributes: { selector: input.selector ?? \\\"body\\\", browserId: browser.browserId } });\\n const selector = input.selector ?? \\\"body\\\";\\n const text = await page.evaluate((currentSelector) => {\\n return document.querySelector(currentSelector)?.textContent ?? \\\"\\\";\\n }, selector);\\n return {\\n pageUrl: page.url(),\\n traceContext,\\n text,\\n };\\n};\\n```\\n\"","/**\n * CustomScriptAuthoringPrompt\n *\n * Guides an AI assistant to create browse-tool custom tool folders that work\n * with the `mcp-serve --custom-tools <dir>` flow.\n */\nimport { Liquid } from 'liquidjs';\nimport promptTemplate from './instructions/custom-script-authoring.md?raw';\n\nexport const customScriptAuthoringPrompt = {\n name: 'custom_script_authoring',\n description: 'Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.',\n arguments: [\n {\n name: 'toolGoal',\n description: 'What the custom tool should do on the page',\n required: true,\n },\n {\n name: 'toolName',\n description: 'Preferred snake_case tool name',\n required: false,\n },\n {\n name: 'pageContext',\n description: 'Optional context about the target page or workflow',\n required: false,\n },\n ],\n};\n\ninterface GenerateCustomScriptAuthoringPromptArgs {\n toolGoal: string;\n toolName?: string;\n pageContext?: string;\n}\n\nconst liquid = new Liquid();\n\nexport function generateCustomScriptAuthoringPrompt(\n args: GenerateCustomScriptAuthoringPromptArgs,\n): Array<{ role: string; content: { type: string; text: string } }> {\n const preferredToolName = args.toolName?.trim() || 'custom_page_tool';\n const pageContext = args.pageContext?.trim() || 'No extra page context provided.';\n const text = liquid.parseAndRenderSync(promptTemplate, {\n toolGoal: args.toolGoal,\n preferredToolName,\n pageContext,\n });\n\n return [\n {\n role: 'user',\n content: {\n type: 'text',\n text,\n },\n },\n ];\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const e=require(`./streamable-http-CDH-wHjE.cjs`);let t=require(`node:module`);const n=Symbol.for(`__locatorProxyBrand__`);var r=class e{[n]=!0;constructor(e,t){this.page=e,this.steps=t}getByRole(t,n){return new e(this.page,[...this.steps,{type:`role`,role:t,options:n}])}getByText(t,n){return new e(this.page,[...this.steps,{type:`text`,text:t,options:n}])}getByLabel(t,n){return new e(this.page,[...this.steps,{type:`label`,text:t,options:n}])}getByPlaceholder(t,n){return new e(this.page,[...this.steps,{type:`placeholder`,text:t,options:n}])}getByTestId(t){return new e(this.page,[...this.steps,{type:`testId`,testId:t}])}locator(t){return new e(this.page,[...this.steps,{type:`css`,selector:t}])}filter(t){return new e(this.page,[...this.steps,{type:`filter`,options:t}])}first(){return new e(this.page,[...this.steps,{type:`first`}])}last(){return this.nth(-1)}nth(t){return new e(this.page,[...this.steps,{type:`nth`,index:t}])}async click(e){let t=await this.resolveAndMark(e?.timeout);await this.page.click(t)}async fill(e,t){let n=await this.resolveAndMark(t?.timeout);await this.page.fill(n,e)}async type(e,t){let n=await this.resolveAndMark(t?.timeout);await this.page.type(n,e,{delay:t?.delay})}async press(e){let t=await this.resolveAndMark();await this.page.click(t),await this.page.press(e)}async hover(e){let t=await this.resolveAndMark(e?.timeout);await this.page.hover(t)}async selectOption(e){let t=await this.resolveAndMark();await this.page.selectOption(t,e)}async check(){let e=await this.resolveAndMark();await this.page.click(e)}async uncheck(){let e=await this.resolveAndMark();await this.page.click(e)}async isVisible(){let e=this.buildResolveScript(`isVisible`);return await this.page.evaluate(e)??!1}async isHidden(){return!await this.isVisible()}async isEnabled(){return!await this.evaluateProperty(`disabled`)}async isDisabled(){return!!await this.evaluateProperty(`disabled`)}async isChecked(){return!!await this.evaluateProperty(`checked`)}async evaluateProperty(e){let t=this.buildResolveScript(`getProperty`,e);return this.page.evaluate(t)}async textContent(){let e=this.buildResolveScript(`textContent`);return this.page.evaluate(e)}async innerText(){let e=this.buildResolveScript(`innerText`);return await this.page.evaluate(e)??``}async inputValue(){let e=this.buildResolveScript(`inputValue`);return await this.page.evaluate(e)??``}async count(){let e=this.buildResolveScript(`count`);return await this.page.evaluate(e)??0}async waitFor(e){let t=e?.timeout??5e3,n=e?.state??`visible`,r=Date.now();for(;Date.now()-r<t;){if(n===`visible`&&await this.isVisible()||n===`hidden`&&!await this.isVisible()||n===`attached`&&await this.count()>0||n===`detached`&&await this.count()===0)return;await new Promise(e=>setTimeout(e,100))}throw Error(`Locator waitFor("${n}") timed out after ${t}ms`)}async resolveAndMark(e){let t=e??5e3,n=Date.now(),r=`pw-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;for(;Date.now()-n<t;){let e=this.buildResolveScript(`mark`,r);if(await this.page.evaluate(e))return`[data-pw-proxy="${r}"]`;await new Promise(e=>setTimeout(e,100))}throw Error(`Locator could not resolve element within ${t}ms. Steps: ${this.describeSteps()}`)}describeSteps(){return this.steps.map(e=>{switch(e.type){case`role`:return`getByRole("${e.role}"${e.options?.name?`, { name: "${String(e.options.name)}" }`:``})`;case`text`:return`getByText("${String(e.text)}")`;case`label`:return`getByLabel("${String(e.text)}")`;case`placeholder`:return`getByPlaceholder("${String(e.text)}")`;case`testId`:return`getByTestId("${e.testId}")`;case`css`:return`locator("${e.selector}")`;case`filter`:return`filter(...)`;case`first`:return`first()`;case`nth`:return`nth(${e.index})`}}).join(`.`)}serializeSteps(){return this.steps.map(e=>{if(e.type===`text`||e.type===`label`||e.type===`placeholder`)return{...e,text:e.text instanceof RegExp?{__regex__:e.text.source,flags:e.text.flags}:e.text};if(e.type===`role`&&e.options?.name instanceof RegExp)return{...e,options:{...e.options,name:{__regex__:e.options.name.source,flags:e.options.name.flags}}};if(e.type===`filter`){let t={};return e.options.hasText&&(t.hasText=e.options.hasText instanceof RegExp?{__regex__:e.options.hasText.source,flags:e.options.hasText.flags}:e.options.hasText),e.options.has&&(t.hasSteps=e.options.has.serializeSteps()),{type:`filter`,options:t}}return e})}buildResolveScript(e,t){return`(function() {
1
+ const e=require(`./streamable-http-O6YYT3Yr.cjs`);let t=require(`node:module`);const n=Symbol.for(`__locatorProxyBrand__`);var r=class e{[n]=!0;constructor(e,t){this.page=e,this.steps=t}getByRole(t,n){return new e(this.page,[...this.steps,{type:`role`,role:t,options:n}])}getByText(t,n){return new e(this.page,[...this.steps,{type:`text`,text:t,options:n}])}getByLabel(t,n){return new e(this.page,[...this.steps,{type:`label`,text:t,options:n}])}getByPlaceholder(t,n){return new e(this.page,[...this.steps,{type:`placeholder`,text:t,options:n}])}getByTestId(t){return new e(this.page,[...this.steps,{type:`testId`,testId:t}])}locator(t){return new e(this.page,[...this.steps,{type:`css`,selector:t}])}filter(t){return new e(this.page,[...this.steps,{type:`filter`,options:t}])}first(){return new e(this.page,[...this.steps,{type:`first`}])}last(){return this.nth(-1)}nth(t){return new e(this.page,[...this.steps,{type:`nth`,index:t}])}async click(e){let t=await this.resolveAndMark(e?.timeout);await this.page.click(t)}async fill(e,t){let n=await this.resolveAndMark(t?.timeout);await this.page.fill(n,e)}async type(e,t){let n=await this.resolveAndMark(t?.timeout);await this.page.type(n,e,{delay:t?.delay})}async press(e){let t=await this.resolveAndMark();await this.page.click(t),await this.page.press(e)}async hover(e){let t=await this.resolveAndMark(e?.timeout);await this.page.hover(t)}async selectOption(e){let t=await this.resolveAndMark();await this.page.selectOption(t,e)}async check(){let e=await this.resolveAndMark();await this.page.click(e)}async uncheck(){let e=await this.resolveAndMark();await this.page.click(e)}async isVisible(){let e=this.buildResolveScript(`isVisible`);return await this.page.evaluate(e)??!1}async isHidden(){return!await this.isVisible()}async isEnabled(){return!await this.evaluateProperty(`disabled`)}async isDisabled(){return!!await this.evaluateProperty(`disabled`)}async isChecked(){return!!await this.evaluateProperty(`checked`)}async evaluateProperty(e){let t=this.buildResolveScript(`getProperty`,e);return this.page.evaluate(t)}async textContent(){let e=this.buildResolveScript(`textContent`);return this.page.evaluate(e)}async innerText(){let e=this.buildResolveScript(`innerText`);return await this.page.evaluate(e)??``}async inputValue(){let e=this.buildResolveScript(`inputValue`);return await this.page.evaluate(e)??``}async count(){let e=this.buildResolveScript(`count`);return await this.page.evaluate(e)??0}async waitFor(e){let t=e?.timeout??5e3,n=e?.state??`visible`,r=Date.now();for(;Date.now()-r<t;){if(n===`visible`&&await this.isVisible()||n===`hidden`&&!await this.isVisible()||n===`attached`&&await this.count()>0||n===`detached`&&await this.count()===0)return;await new Promise(e=>setTimeout(e,100))}throw Error(`Locator waitFor("${n}") timed out after ${t}ms`)}async resolveAndMark(e){let t=e??5e3,n=Date.now(),r=`pw-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;for(;Date.now()-n<t;){let e=this.buildResolveScript(`mark`,r);if(await this.page.evaluate(e))return`[data-pw-proxy="${r}"]`;await new Promise(e=>setTimeout(e,100))}throw Error(`Locator could not resolve element within ${t}ms. Steps: ${this.describeSteps()}`)}describeSteps(){return this.steps.map(e=>{switch(e.type){case`role`:return`getByRole("${e.role}"${e.options?.name?`, { name: "${String(e.options.name)}" }`:``})`;case`text`:return`getByText("${String(e.text)}")`;case`label`:return`getByLabel("${String(e.text)}")`;case`placeholder`:return`getByPlaceholder("${String(e.text)}")`;case`testId`:return`getByTestId("${e.testId}")`;case`css`:return`locator("${e.selector}")`;case`filter`:return`filter(...)`;case`first`:return`first()`;case`nth`:return`nth(${e.index})`}}).join(`.`)}serializeSteps(){return this.steps.map(e=>{if(e.type===`text`||e.type===`label`||e.type===`placeholder`)return{...e,text:e.text instanceof RegExp?{__regex__:e.text.source,flags:e.text.flags}:e.text};if(e.type===`role`&&e.options?.name instanceof RegExp)return{...e,options:{...e.options,name:{__regex__:e.options.name.source,flags:e.options.name.flags}}};if(e.type===`filter`){let t={};return e.options.hasText&&(t.hasText=e.options.hasText instanceof RegExp?{__regex__:e.options.hasText.source,flags:e.options.hasText.flags}:e.options.hasText),e.options.has&&(t.hasSteps=e.options.has.serializeSteps()),{type:`filter`,options:t}}return e})}buildResolveScript(e,t){return`(function() {
2
2
  var steps = ${JSON.stringify(this.serializeSteps())};
3
3
  var action = "${e}";
4
4
  var extra = ${t===void 0?`null`:JSON.stringify(t)};
@@ -249,5 +249,5 @@ const e=require(`./streamable-http-CDH-wHjE.cjs`);let t=require(`node:module`);c
249
249
  }
250
250
 
251
251
  return null;
252
- })()`}};const i=Symbol.for(`__pageProxyBrand__`),a=100,o=5e3;async function s(e,t,n=5e3){let r=Date.now();for(;Date.now()-r<n;){if(await e())return;await new Promise(e=>setTimeout(e,100))}throw Error(t)}function c(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e===t}function l(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e.includes(t)}function u(e,t){let n=e=>t?!e:e;return{async toBeVisible(r){await s(async()=>n(await e.isVisible()),t?`Expected element to not be visible`:`Expected element to be visible`,r?.timeout)},async toBeHidden(r){await s(async()=>n(!await e.isVisible()),t?`Expected element to not be hidden`:`Expected element to be hidden`,r?.timeout)},async toBeDisabled(r){await s(async()=>n(!!await e.evaluateProperty(`disabled`)),t?`Expected element to not be disabled`:`Expected element to be disabled`,r?.timeout)},async toBeEnabled(r){await s(async()=>n(!await e.evaluateProperty(`disabled`)),t?`Expected element to not be enabled`:`Expected element to be enabled`,r?.timeout)},async toBeChecked(r){await s(async()=>n(!!await e.evaluateProperty(`checked`)),t?`Expected element to not be checked`:`Expected element to be checked`,r?.timeout)},async toHaveValue(r,i){await s(async()=>n(c(await e.evaluateProperty(`value`),r)),t?`Expected element to not have value "${String(r)}"`:`Expected element to have value "${String(r)}"`,i?.timeout)},async toHaveText(r,i){await s(async()=>n(c((await e.textContent())?.trim()??null,r)),t?`Expected element to not have text "${String(r)}"`:`Expected element to have text "${String(r)}"`,i?.timeout)},async toContainText(r,i){await s(async()=>n(l(await e.textContent(),r)),t?`Expected element to not contain text "${String(r)}"`:`Expected element to contain text "${String(r)}"`,i?.timeout)},async toHaveCount(r,i){await s(async()=>n(await e.count()===r),t?`Expected element count to not be ${r}`:`Expected element count to be ${r}`,i?.timeout)},get not(){return u(e,!t)}}}function d(e,t){let n=e=>t?!e:e;return{async toHaveURL(r,i){await s(async()=>n(c(await e.currentUrlAsync(),r)),t?`Expected page to not have URL "${String(r)}"`:`Expected page to have URL "${String(r)}"`,i?.timeout)},async toHaveTitle(r,i){await s(async()=>n(c(await e.title(),r)),t?`Expected page to not have title "${String(r)}"`:`Expected page to have title "${String(r)}"`,i?.timeout)},get not(){return d(e,!t)}}}function f(e){let t=e;if(t[n])return u(e,!1);if(t[i])return d(e,!1);throw Error(`createExtensionExpect called with unsupported target`)}function p(){try{return(0,t.createRequire)(require(`url`).pathToFileURL(__filename).href).resolve(`playwright/test`),require(`url`).pathToFileURL(__filename).href}catch{return typeof __PLAYWRIGHT_MCP_STUB_PATH__==`string`?`file://${__PLAYWRIGHT_MCP_STUB_PATH__}`:require(`url`).pathToFileURL(__filename).href}}const m=process,h=m.__pw_initiator__;m.__pw_initiator__=void 0;const g=(0,t.createRequire)(p())(`playwright/test`),{expect:_}=g;m.__pw_initiator__=h;const{request:v,chromium:y,firefox:b,webkit:x,devices:S,selectors:C,defineConfig:w,mergeExpects:T,mergeTests:E}=g;var D=class{currentSpecPath=``;rootDescribe;currentDescribe;constructor(){this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}createDescribeBlock(e,t){return{title:e,tests:[],beforeEachHooks:[],afterEachHooks:[],children:[],parent:t}}setSpecPath(e){this.currentSpecPath=e}reset(){this.currentSpecPath=``,this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}addTest(e,t,n=!1,r=!1){let i={title:e,fn:t,fullTitle:this.getFullTitle(e),only:n,skip:r};return this.currentDescribe.tests.push(i),i}enterDescribe(e){let t=this.createDescribeBlock(e,this.currentDescribe);this.currentDescribe.children.push(t),this.currentDescribe=t}exitDescribe(){this.currentDescribe.parent&&(this.currentDescribe=this.currentDescribe.parent)}addBeforeEach(e){this.currentDescribe.beforeEachHooks.push(e)}addAfterEach(e){this.currentDescribe.afterEachHooks.push(e)}markCurrentDescribeSkipped(){for(let e of this.currentDescribe.tests)e.skip=!0;for(let e of this.currentDescribe.children)this.markDescribeSkipped(e)}markDescribeSkipped(e){for(let t of e.tests)t.skip=!0;for(let t of e.children)this.markDescribeSkipped(t)}getFullTitle(e){let t=[],n=this.currentDescribe;for(;n;)n.title&&t.unshift(n.title),n=n.parent;return t.push(e),t.join(` > `)}flattenTests(e,t,n){let r=[],i=[...t,...e.beforeEachHooks],a=[...e.afterEachHooks,...n];for(let t of e.tests)r.push({title:t.title,fullTitle:t.fullTitle,fn:async e=>{for(let t of i)await t(e);await t.fn(e);for(let t of a)await t(e)},only:t.only,skip:t.skip});for(let t of e.children)r.push(...this.flattenTests(t,i,a));return r}retrieveAndReset(){let e=this.flattenTests(this.rootDescribe,[],[]),t={specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length};return this.reset(),t}peek(){let e=this.flattenTests(this.rootDescribe,[],[]);return{specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length}}},O=class extends Error{isSkip=!0;constructor(e){super(e??`Skipped`),this.name=`SkipTestError`}};const k=`__playwrightMcpTestCollector__`,A=globalThis;A[k]||(A[k]=new D);const j=A[k];function M(e){let t=new Map;for(let[n,r]of Object.entries(e))typeof r==`function`?t.set(n,r):Array.isArray(r)&&r.length===2&&typeof r[1]==`function`&&t.set(n,r[1]);return t}function N(e){if(e.size===0)return e=>e;let t=[...e.entries()];return e=>async n=>{let r={...n};async function i(n){if(n>=t.length){await e(r);return}let[a,o]=t[n];await o(r,async e=>{r[a]=e,await i(n+1)})}await i(0)}}function P(e){let t=N(e);function n(e,n){j.addTest(e,t(n))}return n.only=function(e,n){j.addTest(e,t(n),!0,!1)},n.skip=function(e,n){if(e===void 0||e===!0)throw new O(typeof n==`string`?n:`Skipped`);e!==!1&&typeof e==`string`&&typeof n==`function`&&j.addTest(e,t(n),!1,!0)},n.describe=function(e,t){j.enterDescribe(e);try{t()}catch(e){if(e instanceof O)j.markCurrentDescribeSkipped();else throw e}j.exitDescribe()},n.beforeEach=function(e){j.addBeforeEach(t(e))},n.afterEach=function(e){j.addAfterEach(t(e))},n.extend=function(t){return P(new Map([...e,...M(t)]))},n}function F(e,t){j.addTest(e,t)}function I(e,t){j.addTest(e,t,!0,!1)}function L(e,t){if(e===void 0||e===!0)throw new O(typeof t==`string`?t:`Skipped`);e!==!1&&typeof e==`string`&&typeof t==`function`&&j.addTest(e,t,!1,!0)}function R(e,t){j.enterDescribe(e);try{t()}catch(e){if(e instanceof O)j.markCurrentDescribeSkipped();else throw e}j.exitDescribe()}function z(e){j.addBeforeEach(e)}function B(e){j.addAfterEach(e)}F.describe=R,F.beforeEach=z,F.afterEach=B,F.only=I,F.skip=L,F.extend=function(e){return P(M(e))};function V(e){j.reset(),j.setSpecPath(e)}function H(){return j.retrieveAndReset()}function U(){return j.peek()}function W(){j.reset()}const G=(e=>{let t=e;return t?.[n]||t?.[i]?f(e):_(e)});Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return G}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return U}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return H}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return r}});
253
- //# sourceMappingURL=playwright-test-BicczY9j.cjs.map
252
+ })()`}};const i=Symbol.for(`__pageProxyBrand__`),a=100,o=5e3;async function s(e,t,n=5e3){let r=Date.now();for(;Date.now()-r<n;){if(await e())return;await new Promise(e=>setTimeout(e,100))}throw Error(t)}function c(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e===t}function l(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e.includes(t)}function u(e,t){let n=e=>t?!e:e;return{async toBeVisible(r){await s(async()=>n(await e.isVisible()),t?`Expected element to not be visible`:`Expected element to be visible`,r?.timeout)},async toBeHidden(r){await s(async()=>n(!await e.isVisible()),t?`Expected element to not be hidden`:`Expected element to be hidden`,r?.timeout)},async toBeDisabled(r){await s(async()=>n(!!await e.evaluateProperty(`disabled`)),t?`Expected element to not be disabled`:`Expected element to be disabled`,r?.timeout)},async toBeEnabled(r){await s(async()=>n(!await e.evaluateProperty(`disabled`)),t?`Expected element to not be enabled`:`Expected element to be enabled`,r?.timeout)},async toBeChecked(r){await s(async()=>n(!!await e.evaluateProperty(`checked`)),t?`Expected element to not be checked`:`Expected element to be checked`,r?.timeout)},async toHaveValue(r,i){await s(async()=>n(c(await e.evaluateProperty(`value`),r)),t?`Expected element to not have value "${String(r)}"`:`Expected element to have value "${String(r)}"`,i?.timeout)},async toHaveText(r,i){await s(async()=>n(c((await e.textContent())?.trim()??null,r)),t?`Expected element to not have text "${String(r)}"`:`Expected element to have text "${String(r)}"`,i?.timeout)},async toContainText(r,i){await s(async()=>n(l(await e.textContent(),r)),t?`Expected element to not contain text "${String(r)}"`:`Expected element to contain text "${String(r)}"`,i?.timeout)},async toHaveCount(r,i){await s(async()=>n(await e.count()===r),t?`Expected element count to not be ${r}`:`Expected element count to be ${r}`,i?.timeout)},get not(){return u(e,!t)}}}function d(e,t){let n=e=>t?!e:e;return{async toHaveURL(r,i){await s(async()=>n(c(await e.currentUrlAsync(),r)),t?`Expected page to not have URL "${String(r)}"`:`Expected page to have URL "${String(r)}"`,i?.timeout)},async toHaveTitle(r,i){await s(async()=>n(c(await e.title(),r)),t?`Expected page to not have title "${String(r)}"`:`Expected page to have title "${String(r)}"`,i?.timeout)},get not(){return d(e,!t)}}}function f(e){let t=e;if(t[n])return u(e,!1);if(t[i])return d(e,!1);throw Error(`createExtensionExpect called with unsupported target`)}function p(){try{return(0,t.createRequire)(require(`url`).pathToFileURL(__filename).href).resolve(`playwright/test`),require(`url`).pathToFileURL(__filename).href}catch{return typeof __PLAYWRIGHT_MCP_STUB_PATH__==`string`?`file://${__PLAYWRIGHT_MCP_STUB_PATH__}`:require(`url`).pathToFileURL(__filename).href}}const m=process,h=m.__pw_initiator__;m.__pw_initiator__=void 0;const g=(0,t.createRequire)(p())(`playwright/test`),{expect:_}=g;m.__pw_initiator__=h;const{request:v,chromium:y,firefox:b,webkit:x,devices:S,selectors:C,defineConfig:w,mergeExpects:T,mergeTests:E}=g;var D=class e{constructor(e,t=null){this.defs=e,this.parent=t}getAllDefs(){let e=this.parent?.getAllDefs()??new Map;return new Map([...e,...this.defs])}extend(t){return new e(t,this)}},O=class{currentSpecPath=``;rootDescribe;currentDescribe;constructor(){this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}createDescribeBlock(e,t){return{title:e,tests:[],beforeEachHooks:[],afterEachHooks:[],children:[],parent:t,fixtureScope:null}}setSpecPath(e){this.currentSpecPath=e}reset(){this.currentSpecPath=``,this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}addTest(e,t,n=!1,r=!1){let i={title:e,fn:t,fullTitle:this.getFullTitle(e),only:n,skip:r};return this.currentDescribe.tests.push(i),i}ensureFixtureScope(e){if(e.size===0||this.currentDescribe.fixtureScope)return;let t=null,n=this.currentDescribe.parent;for(;n;){if(n.fixtureScope){t=n.fixtureScope;break}n=n.parent}this.currentDescribe.fixtureScope=new D(e,t)}enterDescribe(e){let t=this.createDescribeBlock(e,this.currentDescribe);this.currentDescribe.children.push(t),this.currentDescribe=t}exitDescribe(){this.currentDescribe.parent&&(this.currentDescribe=this.currentDescribe.parent)}addBeforeAll(e){this.currentDescribe.beforeAllHooks||(this.currentDescribe.beforeAllHooks=[]),this.currentDescribe.beforeAllHooks.push(e)}addAfterAll(e){this.currentDescribe.afterAllHooks||(this.currentDescribe.afterAllHooks=[]),this.currentDescribe.afterAllHooks.push(e)}addBeforeEach(e){this.currentDescribe.beforeEachHooks.push(e)}addAfterEach(e){this.currentDescribe.afterEachHooks.push(e)}markCurrentDescribeSkipped(){for(let e of this.currentDescribe.tests)e.skip=!0;for(let e of this.currentDescribe.children)this.markDescribeSkipped(e)}markDescribeSkipped(e){for(let t of e.tests)t.skip=!0;for(let t of e.children)this.markDescribeSkipped(t)}getFullTitle(e){let t=[],n=this.currentDescribe;for(;n;)n.title&&t.unshift(n.title),n=n.parent;return t.push(e),t.join(` > `)}collectFixtureDefs(e){return e.fixtureScope?.getAllDefs()??new Map}flattenTests(e,t,n){let r=[],i=[...t,...e.beforeEachHooks],a=[...e.afterEachHooks,...n],o=e.beforeAllHooks??[],s=e.afterAllHooks??[],c=P(this.collectFixtureDefs(e)),l=!1,u=[];for(let t of e.tests)u.push({title:t.title,fullTitle:t.fullTitle,fn:c(async e=>{if(!l&&o.length>0){l=!0;for(let t of o)await t(e)}for(let t of i)await t(e);await t.fn(e);for(let t of a)await t(e)}),only:t.only,skip:t.skip});for(let t of e.children)u.push(...this.flattenTests(t,i,a));if(s.length>0&&u.length>0){let e=u[u.length-1],t=e.fn;e.fn=async e=>{try{await t(e)}finally{for(let t of s)await t(e)}}}return r.push(...u),r}retrieveAndReset(){let e=this.flattenTests(this.rootDescribe,[],[]),t={specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length};return this.reset(),t}peek(){let e=this.flattenTests(this.rootDescribe,[],[]);return{specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length}}},k=class extends Error{isSkip=!0;constructor(e){super(e??`Skipped`),this.name=`SkipTestError`}};const A=`__playwrightMcpTestCollector__`,j=globalThis;j[A]||(j[A]=new O);const M=j[A];function N(e){let t=new Map;for(let[n,r]of Object.entries(e))typeof r==`function`?t.set(n,r):Array.isArray(r)&&r.length===2&&typeof r[1]==`function`&&t.set(n,r[1]);return t}function P(e){if(e.size===0)return e=>e;let t=[...e.entries()];return e=>async n=>{let r={...n};async function i(n){if(n>=t.length){await e(r);return}let[a,o]=t[n];await o(r,async e=>{r[a]=e,await i(n+1)})}await i(0)}}function F(e){function t(){M.ensureFixtureScope(e)}function n(e,n){t(),M.addTest(e,n)}return n.only=function(e,n){t(),M.addTest(e,n,!0,!1)},n.skip=function(e,n){if(e===void 0||e===!0)throw new k(typeof n==`string`?n:`Skipped`);e!==!1&&typeof e==`string`&&typeof n==`function`&&(t(),M.addTest(e,n,!1,!0))},n.describe=function(e,t){M.enterDescribe(e);try{t()}catch(e){if(e instanceof k)M.markCurrentDescribeSkipped();else throw e}M.exitDescribe()},n.beforeAll=function(e){t(),M.addBeforeAll(e)},n.afterAll=function(e){t(),M.addAfterAll(e)},n.beforeEach=function(e){t(),M.addBeforeEach(e)},n.afterEach=function(e){t(),M.addAfterEach(e)},n.extend=function(t){return F(new Map([...e,...N(t)]))},n}function I(e,t){M.addTest(e,t)}function L(e,t){M.addTest(e,t,!0,!1)}function R(e,t){if(e===void 0||e===!0)throw new k(typeof t==`string`?t:`Skipped`);e!==!1&&typeof e==`string`&&typeof t==`function`&&M.addTest(e,t,!1,!0)}function z(e,t){M.enterDescribe(e);try{t()}catch(e){if(e instanceof k)M.markCurrentDescribeSkipped();else throw e}M.exitDescribe()}function B(e){M.addBeforeAll(e)}function V(e){M.addAfterAll(e)}function H(e){M.addBeforeEach(e)}function U(e){M.addAfterEach(e)}I.describe=z,I.beforeAll=B,I.afterAll=V,I.beforeEach=H,I.afterEach=U,I.only=L,I.skip=R,I.extend=function(e){return F(N(e))};function W(e){M.reset(),M.setSpecPath(e)}function G(){return M.retrieveAndReset()}function K(){return M.peek()}function q(){M.reset()}const J=(e=>{let t=e;return t?.[n]||t?.[i]?f(e):_(e)});Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return J}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return I}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return q}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return G}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return r}});
253
+ //# sourceMappingURL=playwright-test-DfpyCIc0.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-test-DfpyCIc0.cjs","names":["page: IExtensionPageProxy","steps: LocatorStep[]","filterOpts: Record<string, unknown>","defs: Map<string, FixtureFunction>","parent: FixtureScope | null","testBlock: TestBlock","parentScope: FixtureScope | null","test","describe","parts: string[]","current: DescribeBlock | null","tests: TestBlock[]","allTestsInBlock: TestBlock[]","suite: TestSuite","resolved: TestFixtures","expect: typeof playwrightExpect"],"sources":["../src/services/LocatorProxy.ts","../src/utils/extension-expect.ts","../src/stubs/playwright-test.ts"],"sourcesContent":["/**\n * LocatorProxy\n *\n * DESIGN PATTERNS:\n * - Proxy pattern for Playwright Locator API compatibility\n * - Builder pattern for chainable locator steps\n * - Adapter pattern to bridge DOM evaluation and MCP browser tools\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Match Playwright Locator interface methods\n * - Route actions through ExtensionPageProxy\n *\n * AVOID:\n * - Blocking operations\n * - Missing error handling\n * - Mutating locator steps (always return new instances)\n */\n\nimport type { IExtensionPageProxy } from './ExtensionPageProxy.js';\n\n/**\n * Brand symbol for identifying LocatorProxy instances in expect()\n */\nexport const LOCATOR_PROXY_BRAND = Symbol.for('__locatorProxyBrand__');\n\n/**\n * A single step in the locator chain\n */\nexport type LocatorStep =\n | { type: 'role'; role: string; options?: { name?: string | RegExp; exact?: boolean } }\n | { type: 'text'; text: string | RegExp; options?: { exact?: boolean } }\n | { type: 'label'; text: string | RegExp; options?: { exact?: boolean } }\n | { type: 'placeholder'; text: string | RegExp; options?: { exact?: boolean } }\n | { type: 'testId'; testId: string }\n | { type: 'css'; selector: string }\n | { type: 'filter'; options: { hasText?: string | RegExp; has?: LocatorProxy } }\n | { type: 'first' }\n | { type: 'nth'; index: number };\n\n/**\n * Action types that buildResolveScript can execute\n */\ntype ResolveAction = 'mark' | 'isVisible' | 'getProperty' | 'textContent' | 'count' | 'innerText' | 'inputValue';\n\n/**\n * Chainable locator proxy that mimics Playwright's Locator API.\n * Accumulates locator steps and resolves them in-page at action time.\n */\nexport class LocatorProxy {\n readonly [LOCATOR_PROXY_BRAND] = true;\n\n constructor(\n private readonly page: IExtensionPageProxy,\n private readonly steps: LocatorStep[],\n ) {}\n\n // ── Chaining methods ──────────────────────────────────────────────\n\n getByRole(role: string, options?: { name?: string | RegExp; exact?: boolean }): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'role', role, options }]);\n }\n\n getByText(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'text', text, options }]);\n }\n\n getByLabel(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'label', text, options }]);\n }\n\n getByPlaceholder(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'placeholder', text, options }]);\n }\n\n getByTestId(testId: string): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'testId', testId }]);\n }\n\n locator(selector: string): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'css', selector }]);\n }\n\n filter(options: { hasText?: string | RegExp; has?: LocatorProxy }): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'filter', options }]);\n }\n\n first(): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'first' }]);\n }\n\n last(): LocatorProxy {\n return this.nth(-1);\n }\n\n nth(index: number): LocatorProxy {\n return new LocatorProxy(this.page, [...this.steps, { type: 'nth', index }]);\n }\n\n // ── Action methods ────────────────────────────────────────────────\n\n async click(options?: { timeout?: number }): Promise<void> {\n const selector = await this.resolveAndMark(options?.timeout);\n await this.page.click(selector);\n }\n\n async fill(value: string, options?: { timeout?: number }): Promise<void> {\n const selector = await this.resolveAndMark(options?.timeout);\n await this.page.fill(selector, value);\n }\n\n async type(text: string, options?: { delay?: number; timeout?: number }): Promise<void> {\n const selector = await this.resolveAndMark(options?.timeout);\n await this.page.type(selector, text, { delay: options?.delay });\n }\n\n async press(key: string): Promise<void> {\n const selector = await this.resolveAndMark();\n await this.page.click(selector);\n await this.page.press(key);\n }\n\n async hover(options?: { timeout?: number }): Promise<void> {\n const selector = await this.resolveAndMark(options?.timeout);\n await this.page.hover(selector);\n }\n\n async selectOption(values: string | string[]): Promise<void> {\n const selector = await this.resolveAndMark();\n await this.page.selectOption(selector, values);\n }\n\n async check(): Promise<void> {\n const selector = await this.resolveAndMark();\n await this.page.click(selector);\n }\n\n async uncheck(): Promise<void> {\n const selector = await this.resolveAndMark();\n await this.page.click(selector);\n }\n\n async isVisible(): Promise<boolean> {\n const script = this.buildResolveScript('isVisible');\n const result = await this.page.evaluate<boolean>(script);\n return result ?? false;\n }\n\n async isHidden(): Promise<boolean> {\n return !(await this.isVisible());\n }\n\n async isEnabled(): Promise<boolean> {\n const disabled = await this.evaluateProperty('disabled');\n return !disabled;\n }\n\n async isDisabled(): Promise<boolean> {\n const disabled = await this.evaluateProperty('disabled');\n return !!disabled;\n }\n\n async isChecked(): Promise<boolean> {\n const checked = await this.evaluateProperty('checked');\n return !!checked;\n }\n\n // ── Helper / query methods ────────────────────────────────────────\n\n async evaluateProperty(prop: string): Promise<unknown> {\n const script = this.buildResolveScript('getProperty', prop);\n return this.page.evaluate<unknown>(script);\n }\n\n async textContent(): Promise<string | null> {\n const script = this.buildResolveScript('textContent');\n return this.page.evaluate<string | null>(script);\n }\n\n async innerText(): Promise<string> {\n const script = this.buildResolveScript('innerText');\n const result = await this.page.evaluate<string>(script);\n return result ?? '';\n }\n\n async inputValue(): Promise<string> {\n const script = this.buildResolveScript('inputValue');\n const result = await this.page.evaluate<string>(script);\n return result ?? '';\n }\n\n async count(): Promise<number> {\n const script = this.buildResolveScript('count');\n const result = await this.page.evaluate<number>(script);\n return result ?? 0;\n }\n\n async waitFor(options?: { state?: 'visible' | 'hidden' | 'attached' | 'detached'; timeout?: number }): Promise<void> {\n const timeout = options?.timeout ?? 5000;\n const state = options?.state ?? 'visible';\n const start = Date.now();\n const pollInterval = 100;\n\n while (Date.now() - start < timeout) {\n if (state === 'visible' && (await this.isVisible())) return;\n if (state === 'hidden' && !(await this.isVisible())) return;\n if (state === 'attached' && (await this.count()) > 0) return;\n if (state === 'detached' && (await this.count()) === 0) return;\n await new Promise((r) => setTimeout(r, pollInterval));\n }\n\n throw new Error(`Locator waitFor(\"${state}\") timed out after ${timeout}ms`);\n }\n\n // ── Internal ──────────────────────────────────────────────────────\n\n /**\n * Resolves the locator chain in the page, marks the target element\n * with a data-pw-proxy attribute, and returns the CSS selector.\n */\n private async resolveAndMark(timeout?: number): Promise<string> {\n const effectiveTimeout = timeout ?? 5000;\n const start = Date.now();\n const pollInterval = 100;\n const markId = `pw-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n\n while (Date.now() - start < effectiveTimeout) {\n const script = this.buildResolveScript('mark', markId);\n const found = await this.page.evaluate<boolean>(script);\n if (found) {\n return `[data-pw-proxy=\"${markId}\"]`;\n }\n await new Promise((r) => setTimeout(r, pollInterval));\n }\n\n throw new Error(`Locator could not resolve element within ${effectiveTimeout}ms. Steps: ${this.describeSteps()}`);\n }\n\n /**\n * Human-readable description of the locator chain for error messages.\n */\n private describeSteps(): string {\n return this.steps\n .map((step) => {\n switch (step.type) {\n case 'role':\n return `getByRole(\"${step.role}\"${step.options?.name ? `, { name: \"${String(step.options.name)}\" }` : ''})`;\n case 'text':\n return `getByText(\"${String(step.text)}\")`;\n case 'label':\n return `getByLabel(\"${String(step.text)}\")`;\n case 'placeholder':\n return `getByPlaceholder(\"${String(step.text)}\")`;\n case 'testId':\n return `getByTestId(\"${step.testId}\")`;\n case 'css':\n return `locator(\"${step.selector}\")`;\n case 'filter':\n return 'filter(...)';\n case 'first':\n return 'first()';\n case 'nth':\n return `nth(${step.index})`;\n }\n })\n .join('.');\n }\n\n /**\n * Serializes locator steps to JSON-safe format, converting RegExp\n * instances to { __regex__, flags } objects.\n */\n private serializeSteps(): unknown[] {\n return this.steps.map((step) => {\n if (step.type === 'text' || step.type === 'label' || step.type === 'placeholder') {\n return {\n ...step,\n text: step.text instanceof RegExp ? { __regex__: step.text.source, flags: step.text.flags } : step.text,\n };\n }\n if (step.type === 'role' && step.options?.name instanceof RegExp) {\n return {\n ...step,\n options: {\n ...step.options,\n name: { __regex__: step.options.name.source, flags: step.options.name.flags },\n },\n };\n }\n if (step.type === 'filter') {\n const filterOpts: Record<string, unknown> = {};\n if (step.options.hasText) {\n filterOpts.hasText =\n step.options.hasText instanceof RegExp\n ? { __regex__: step.options.hasText.source, flags: step.options.hasText.flags }\n : step.options.hasText;\n }\n if (step.options.has) {\n filterOpts.hasSteps = (step.options.has as LocatorProxy).serializeSteps();\n }\n return { type: 'filter', options: filterOpts };\n }\n return step;\n });\n }\n\n /**\n * Builds a self-contained JavaScript script that runs in the page context.\n * Resolves the locator step chain using DOM APIs and returns the result\n * based on the requested action.\n */\n private buildResolveScript(action: ResolveAction, extra?: string): string {\n const stepsJson = JSON.stringify(this.serializeSteps());\n\n return `(function() {\n var steps = ${stepsJson};\n var action = \"${action}\";\n var extra = ${extra !== undefined ? JSON.stringify(extra) : 'null'};\n\n function toRegex(v) {\n if (v && typeof v === 'object' && v.__regex__) return new RegExp(v.__regex__, v.flags || '');\n return null;\n }\n\n function matchText(actual, expected, exact) {\n if (!actual) return false;\n var regex = toRegex(expected);\n if (regex) return regex.test(actual);\n if (exact) return actual === expected;\n return actual.toLowerCase().indexOf(expected.toLowerCase()) !== -1;\n }\n\n var ROLE_MAP = {\n heading: 'h1,h2,h3,h4,h5,h6,[role=\"heading\"]',\n link: 'a[href],[role=\"link\"]',\n button: 'button,input[type=\"button\"],input[type=\"submit\"],input[type=\"reset\"],[role=\"button\"]',\n textbox: 'input:not([type]),input[type=\"text\"],input[type=\"email\"],input[type=\"password\"],input[type=\"search\"],input[type=\"tel\"],input[type=\"url\"],input[type=\"number\"],textarea,[role=\"textbox\"]',\n checkbox: 'input[type=\"checkbox\"],[role=\"checkbox\"]',\n radio: 'input[type=\"radio\"],[role=\"radio\"]',\n combobox: 'select,[role=\"combobox\"],[role=\"listbox\"]',\n navigation: 'nav,[role=\"navigation\"]',\n main: 'main,[role=\"main\"]',\n banner: 'header,[role=\"banner\"]',\n contentinfo: 'footer,[role=\"contentinfo\"]',\n region: 'section[aria-label],section[aria-labelledby],[role=\"region\"]',\n list: 'ul,ol,[role=\"list\"]',\n listitem: 'li,[role=\"listitem\"]',\n img: 'img,[role=\"img\"]',\n dialog: 'dialog,[role=\"dialog\"],[role=\"alertdialog\"]',\n tab: '[role=\"tab\"]',\n tablist: '[role=\"tablist\"]',\n tabpanel: '[role=\"tabpanel\"]',\n cell: 'td,[role=\"cell\"]',\n row: 'tr,[role=\"row\"]',\n table: 'table,[role=\"table\"],[role=\"grid\"]',\n menuitem: '[role=\"menuitem\"]',\n menu: '[role=\"menu\"],[role=\"menubar\"]',\n switch: '[role=\"switch\"]',\n alert: '[role=\"alert\"]',\n status: '[role=\"status\"]',\n progressbar: 'progress,[role=\"progressbar\"]',\n spinbutton: 'input[type=\"number\"],[role=\"spinbutton\"]',\n slider: 'input[type=\"range\"],[role=\"slider\"]',\n separator: 'hr,[role=\"separator\"]',\n tooltip: '[role=\"tooltip\"]',\n tree: '[role=\"tree\"]',\n treeitem: '[role=\"treeitem\"]',\n group: 'fieldset,[role=\"group\"]',\n article: 'article,[role=\"article\"]',\n complementary: 'aside,[role=\"complementary\"]',\n form: 'form,[role=\"form\"]',\n search: '[role=\"search\"]'\n };\n\n function getAccessibleName(el) {\n if (el.getAttribute('aria-label')) return el.getAttribute('aria-label');\n var labelledBy = el.getAttribute('aria-labelledby');\n if (labelledBy) {\n var labelEl = document.getElementById(labelledBy);\n if (labelEl) return labelEl.textContent.trim();\n }\n if (el.id) {\n var labels = document.querySelectorAll('label[for=\"' + el.id + '\"]');\n if (labels.length > 0) return labels[0].textContent.trim();\n }\n var parentLabel = el.closest('label');\n if (parentLabel) {\n var clone = parentLabel.cloneNode(true);\n var inputs = clone.querySelectorAll('input,select,textarea');\n inputs.forEach(function(inp) { inp.remove(); });\n var t = clone.textContent.trim();\n if (t) return t;\n }\n if (el.placeholder) return el.placeholder;\n if (el.title) return el.title;\n if (el.alt) return el.alt;\n return (el.textContent || '').trim();\n }\n\n function resolveSteps(candidates, stepsToResolve) {\n for (var i = 0; i < stepsToResolve.length; i++) {\n var step = stepsToResolve[i];\n\n if (step.type === 'role') {\n var sel = ROLE_MAP[step.role] || '[role=\"' + step.role + '\"]';\n var matching = [];\n for (var c = 0; c < candidates.length; c++) {\n var children = candidates[c].querySelectorAll(sel);\n for (var j = 0; j < children.length; j++) matching.push(children[j]);\n if (candidates[c].matches && candidates[c].matches(sel)) matching.push(candidates[c]);\n }\n if (step.options && step.options.name != null) {\n var name = step.options.name;\n var exact = step.options.exact || false;\n matching = matching.filter(function(el) {\n return matchText(getAccessibleName(el), name, exact);\n });\n }\n candidates = matching;\n\n } else if (step.type === 'text') {\n var textFiltered = [];\n for (var ci = 0; ci < candidates.length; ci++) {\n var allEls = candidates[ci].querySelectorAll('*');\n for (var ji = 0; ji < allEls.length; ji++) {\n if (matchText(allEls[ji].textContent, step.text, step.options && step.options.exact)) {\n textFiltered.push(allEls[ji]);\n }\n }\n if (matchText(candidates[ci].textContent, step.text, step.options && step.options.exact)) {\n textFiltered.push(candidates[ci]);\n }\n }\n candidates = textFiltered;\n\n } else if (step.type === 'label') {\n var labeled = [];\n var allLabels = document.querySelectorAll('label');\n for (var li = 0; li < allLabels.length; li++) {\n var labelText = allLabels[li].textContent.trim();\n if (matchText(labelText, step.text, step.options && step.options.exact)) {\n var forAttr = allLabels[li].getAttribute('for');\n if (forAttr) {\n var target = document.getElementById(forAttr);\n if (target) labeled.push(target);\n }\n var inner = allLabels[li].querySelectorAll('input,select,textarea');\n for (var ki = 0; ki < inner.length; ki++) labeled.push(inner[ki]);\n }\n }\n var byAria = document.querySelectorAll('[aria-label]');\n for (var ai = 0; ai < byAria.length; ai++) {\n if (matchText(byAria[ai].getAttribute('aria-label'), step.text, step.options && step.options.exact)) {\n labeled.push(byAria[ai]);\n }\n }\n candidates = labeled;\n\n } else if (step.type === 'placeholder') {\n var phMatches = [];\n for (var pi = 0; pi < candidates.length; pi++) {\n var phInputs = candidates[pi].querySelectorAll('[placeholder]');\n for (var pj = 0; pj < phInputs.length; pj++) {\n if (matchText(phInputs[pj].placeholder, step.text, step.options && step.options.exact)) {\n phMatches.push(phInputs[pj]);\n }\n }\n if (candidates[pi].placeholder && matchText(candidates[pi].placeholder, step.text, step.options && step.options.exact)) {\n phMatches.push(candidates[pi]);\n }\n }\n candidates = phMatches;\n\n } else if (step.type === 'testId') {\n var tidMatches = [];\n for (var ti = 0; ti < candidates.length; ti++) {\n var tidFound = candidates[ti].querySelectorAll('[data-testid=\"' + step.testId + '\"]');\n for (var tj = 0; tj < tidFound.length; tj++) tidMatches.push(tidFound[tj]);\n if (candidates[ti].getAttribute && candidates[ti].getAttribute('data-testid') === step.testId) {\n tidMatches.push(candidates[ti]);\n }\n }\n candidates = tidMatches;\n\n } else if (step.type === 'css') {\n var cssMatches = [];\n for (var si = 0; si < candidates.length; si++) {\n var cssFound = candidates[si].querySelectorAll(step.selector);\n for (var sj = 0; sj < cssFound.length; sj++) cssMatches.push(cssFound[sj]);\n }\n candidates = cssMatches;\n\n } else if (step.type === 'filter') {\n if (step.options.hasText) {\n candidates = candidates.filter(function(el) {\n return matchText(el.textContent, step.options.hasText, false);\n });\n }\n if (step.options.hasSteps) {\n candidates = candidates.filter(function(el) {\n var sub = resolveSteps([el], step.options.hasSteps);\n return sub.length > 0;\n });\n }\n\n } else if (step.type === 'first') {\n candidates = candidates.length > 0 ? [candidates[0]] : [];\n\n } else if (step.type === 'nth') {\n var idx = step.index;\n if (idx < 0) idx = candidates.length + idx;\n candidates = (idx >= 0 && idx < candidates.length) ? [candidates[idx]] : [];\n }\n\n candidates = candidates.filter(function(el, elIdx, arr) {\n return arr.indexOf(el) === elIdx;\n });\n }\n return candidates;\n }\n\n var candidates = resolveSteps([document], steps);\n\n if (action === 'count') return candidates.length;\n\n if (action === 'isVisible') {\n if (candidates.length === 0) return false;\n var el = candidates[0];\n var rect = el.getBoundingClientRect();\n var style = window.getComputedStyle(el);\n return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none';\n }\n\n if (candidates.length === 0) {\n if (action === 'textContent') return null;\n if (action === 'innerText') return '';\n if (action === 'inputValue') return '';\n if (action === 'getProperty') return null;\n return false;\n }\n\n var target = candidates[0];\n\n if (action === 'mark') {\n target.setAttribute('data-pw-proxy', extra);\n return true;\n }\n\n if (action === 'getProperty') {\n return target[extra];\n }\n\n if (action === 'textContent') {\n return target.textContent;\n }\n\n if (action === 'innerText') {\n return target.innerText || target.textContent || '';\n }\n\n if (action === 'inputValue') {\n return target.value || '';\n }\n\n return null;\n})()`;\n }\n}\n","/**\n * Extension Expect - Custom assertion wrapper for proxy objects\n *\n * DESIGN PATTERNS:\n * - Adapter pattern to bridge Playwright expect with proxy objects\n * - Polling pattern for async DOM assertions\n * - Pure functions with no side effects (except polling)\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Keep functions small and focused\n *\n * AVOID:\n * - Direct Playwright expect usage on proxy objects\n * - Blocking operations without timeout\n * - Missing negation support\n */\n\nimport { LOCATOR_PROXY_BRAND, type LocatorProxy } from '../services/LocatorProxy.js';\n\n/**\n * Brand symbol for identifying ExtensionPageProxy instances\n */\nexport const PAGE_PROXY_BRAND = Symbol.for('__pageProxyBrand__');\n\n/**\n * Interface for page proxy objects that support URL checking\n */\ninterface PageProxyLike {\n [PAGE_PROXY_BRAND]: boolean;\n currentUrlAsync(): Promise<string>;\n}\n\n/**\n * Polling configuration\n */\nconst POLL_INTERVAL_MS = 100;\nconst DEFAULT_TIMEOUT_MS = 5000;\n\n/**\n * Polls a condition until it passes or times out.\n * Throws assertion error on timeout.\n */\nasync function pollUntil(\n check: () => Promise<boolean>,\n errorMessage: string,\n timeout: number = DEFAULT_TIMEOUT_MS,\n): Promise<void> {\n const start = Date.now();\n\n while (Date.now() - start < timeout) {\n const passed = await check();\n if (passed) return;\n await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));\n }\n\n throw new Error(errorMessage);\n}\n\n/**\n * Assertion options with optional timeout\n */\ninterface AssertionOptions {\n timeout?: number;\n}\n\n/**\n * Assertion set for locator proxy objects\n */\ninterface LocatorAssertions {\n toBeVisible(options?: AssertionOptions): Promise<void>;\n toBeHidden(options?: AssertionOptions): Promise<void>;\n toBeDisabled(options?: AssertionOptions): Promise<void>;\n toBeEnabled(options?: AssertionOptions): Promise<void>;\n toBeChecked(options?: AssertionOptions): Promise<void>;\n toHaveValue(expected: string | RegExp, options?: AssertionOptions): Promise<void>;\n toHaveText(expected: string | RegExp, options?: AssertionOptions): Promise<void>;\n toContainText(expected: string | RegExp, options?: AssertionOptions): Promise<void>;\n toHaveCount(expected: number, options?: AssertionOptions): Promise<void>;\n not: LocatorAssertions;\n}\n\n/**\n * Assertion set for page proxy objects\n */\ninterface PageAssertions {\n toHaveURL(expected: string | RegExp, options?: AssertionOptions): Promise<void>;\n toHaveTitle(expected: string | RegExp, options?: AssertionOptions): Promise<void>;\n not: PageAssertions;\n}\n\n/**\n * Checks if a string matches the expected value (string or RegExp)\n */\nfunction matchesExpected(actual: string | null | undefined, expected: string | RegExp): boolean {\n if (actual == null) return false;\n if (expected instanceof RegExp) return expected.test(actual);\n return actual === expected;\n}\n\n/**\n * Checks if a string contains the expected value (string or RegExp)\n */\nfunction containsExpected(actual: string | null | undefined, expected: string | RegExp): boolean {\n if (actual == null) return false;\n if (expected instanceof RegExp) return expected.test(actual);\n return actual.includes(expected);\n}\n\n/**\n * Creates locator assertions for a LocatorProxy target.\n */\nfunction createLocatorAssertions(target: LocatorProxy, negated: boolean): LocatorAssertions {\n const assert = (condition: boolean): boolean => (negated ? !condition : condition);\n\n const assertions: LocatorAssertions = {\n async toBeVisible(options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => assert(await target.isVisible()),\n negated ? 'Expected element to not be visible' : 'Expected element to be visible',\n options?.timeout,\n );\n },\n\n async toBeHidden(options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => assert(!(await target.isVisible())),\n negated ? 'Expected element to not be hidden' : 'Expected element to be hidden',\n options?.timeout,\n );\n },\n\n async toBeDisabled(options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => assert(!!(await target.evaluateProperty('disabled'))),\n negated ? 'Expected element to not be disabled' : 'Expected element to be disabled',\n options?.timeout,\n );\n },\n\n async toBeEnabled(options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => assert(!(await target.evaluateProperty('disabled'))),\n negated ? 'Expected element to not be enabled' : 'Expected element to be enabled',\n options?.timeout,\n );\n },\n\n async toBeChecked(options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => assert(!!(await target.evaluateProperty('checked'))),\n negated ? 'Expected element to not be checked' : 'Expected element to be checked',\n options?.timeout,\n );\n },\n\n async toHaveValue(expected: string | RegExp, options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => {\n const value = (await target.evaluateProperty('value')) as string;\n return assert(matchesExpected(value, expected));\n },\n negated\n ? `Expected element to not have value \"${String(expected)}\"`\n : `Expected element to have value \"${String(expected)}\"`,\n options?.timeout,\n );\n },\n\n async toHaveText(expected: string | RegExp, options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => {\n const text = await target.textContent();\n return assert(matchesExpected(text?.trim() ?? null, expected));\n },\n negated\n ? `Expected element to not have text \"${String(expected)}\"`\n : `Expected element to have text \"${String(expected)}\"`,\n options?.timeout,\n );\n },\n\n async toContainText(expected: string | RegExp, options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => {\n const text = await target.textContent();\n return assert(containsExpected(text, expected));\n },\n negated\n ? `Expected element to not contain text \"${String(expected)}\"`\n : `Expected element to contain text \"${String(expected)}\"`,\n options?.timeout,\n );\n },\n\n async toHaveCount(expected: number, options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => {\n const count = await target.count();\n return assert(count === expected);\n },\n negated ? `Expected element count to not be ${expected}` : `Expected element count to be ${expected}`,\n options?.timeout,\n );\n },\n\n get not(): LocatorAssertions {\n return createLocatorAssertions(target, !negated);\n },\n };\n\n return assertions;\n}\n\n/**\n * Creates page assertions for a page proxy target.\n */\nfunction createPageAssertions(target: PageProxyLike, negated: boolean): PageAssertions {\n const assert = (condition: boolean): boolean => (negated ? !condition : condition);\n\n const assertions: PageAssertions = {\n async toHaveURL(expected: string | RegExp, options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => {\n const currentUrl = await target.currentUrlAsync();\n return assert(matchesExpected(currentUrl, expected));\n },\n negated\n ? `Expected page to not have URL \"${String(expected)}\"`\n : `Expected page to have URL \"${String(expected)}\"`,\n options?.timeout,\n );\n },\n\n async toHaveTitle(expected: string | RegExp, options?: AssertionOptions): Promise<void> {\n await pollUntil(\n async () => {\n const page = target as unknown as { title(): Promise<string> };\n const title = await page.title();\n return assert(matchesExpected(title, expected));\n },\n negated\n ? `Expected page to not have title \"${String(expected)}\"`\n : `Expected page to have title \"${String(expected)}\"`,\n options?.timeout,\n );\n },\n\n get not(): PageAssertions {\n return createPageAssertions(target, !negated);\n },\n };\n\n return assertions;\n}\n\n/**\n * Creates an extension-mode expect wrapper for proxy objects.\n * Detects LocatorProxy and PageProxy via brand symbols and returns\n * the appropriate assertion set with polling semantics.\n */\nexport function createExtensionExpect(target: unknown): LocatorAssertions | PageAssertions {\n const obj = target as Record<symbol, boolean>;\n\n if (obj[LOCATOR_PROXY_BRAND]) {\n return createLocatorAssertions(target as LocatorProxy, false);\n }\n\n if (obj[PAGE_PROXY_BRAND]) {\n return createPageAssertions(target as PageProxyLike, false);\n }\n\n throw new Error('createExtensionExpect called with unsupported target');\n}\n","/**\n * PlaywrightTestStub - Proxy module for @playwright/test\n *\n * DESIGN PATTERNS:\n * - Proxy pattern to intercept Playwright test API calls\n * - Collector pattern to gather test blocks for sequential execution\n * - Module aliasing compatible structure\n *\n * CODING STANDARDS:\n * - Collect test blocks into retrievable TestSuite structure\n * - Support test(), test.describe(), test.beforeEach(), test.afterEach()\n * - Re-export real expect from @playwright/test\n * - Clear collected tests after retrieval\n *\n * AVOID:\n * - Executing tests immediately (defer to SpecRunner)\n * - Modifying the real @playwright/test behavior\n * - Global state that persists across spec files\n */\n\nimport { createRequire } from 'node:module';\nimport type { Browser, BrowserContext, Page } from 'playwright';\nimport { LOCATOR_PROXY_BRAND } from '../services/LocatorProxy.js';\nimport { PAGE_PROXY_BRAND, createExtensionExpect } from '../utils/extension-expect.js';\n\n/**\n * Compile-time constant injected by SpecBundlerService via Bun's `define`.\n * Contains the absolute path to this stub file's real location on disk.\n * Only present when the stub is bundled into a spec output file.\n */\ndeclare const __PLAYWRIGHT_MCP_STUB_PATH__: string;\n\n/**\n * Resolves a base path for createRequire that can find `playwright/test`.\n * When loaded from the stub's real location, import.meta.url works.\n * When bundled into a temp output (e.g. /tmp/browse-tool-bundles/),\n * import.meta.url points to the bundle and playwright can't be found,\n * so we fall back to the compile-time injected stub path.\n */\nfunction resolveRequireBase(): string {\n try {\n const req = createRequire(import.meta.url);\n req.resolve('playwright/test');\n return import.meta.url;\n } catch {\n if (typeof __PLAYWRIGHT_MCP_STUB_PATH__ === 'string') {\n return `file://${__PLAYWRIGHT_MCP_STUB_PATH__}`;\n }\n return import.meta.url;\n }\n}\n\n// Clear Playwright's double-load guard before importing playwright/test\n// to avoid \"Requiring @playwright/test second time\" when the stub is bundled\n// into spec files that are loaded alongside the MCP server's own playwright import\nconst proc = process as unknown as Record<string, unknown>;\nconst savedPwInitiator = proc.__pw_initiator__;\nproc.__pw_initiator__ = undefined;\nconst _require = createRequire(resolveRequireBase());\nconst _realPlaywrightTest = _require('playwright/test') as Record<string, unknown> & {\n expect: typeof import('playwright/test')['expect'];\n};\nconst { expect: playwrightExpect } = _realPlaywrightTest;\nproc.__pw_initiator__ = savedPwInitiator;\n\n/**\n * Re-export all real playwright/test exports that specs may use.\n * Our custom `test` and `expect` override the real ones; everything\n * else (request, chromium, firefox, webkit, devices, selectors,\n * defineConfig, mergeExpects, mergeTests) is passed through unchanged.\n */\nexport const { request, chromium, firefox, webkit, devices, selectors, defineConfig, mergeExpects, mergeTests } =\n _realPlaywrightTest as Record<string, unknown>;\n\n/**\n * Fixtures passed to test functions.\n * Compatible with Playwright's test fixture pattern.\n * Browser is optional for extension mode which uses persistent context.\n */\nexport interface TestFixtures {\n page: Page;\n context: BrowserContext;\n browser?: Browser;\n /** Playwright module instance for creating API request contexts */\n playwright?: typeof import('playwright');\n /** API request context scoped to the test */\n request?: import('playwright').APIRequestContext;\n /** Extended fixtures added via test.extend() */\n [key: string]: unknown;\n}\n\n/**\n * A single test block collected from the spec file.\n * The `fn` stores the RAW test function — fixture resolution happens in flattenTests.\n */\nexport interface TestBlock {\n /** Test title */\n title: string;\n /** Test function (raw — no fixture wrapping at collection time) */\n fn: (fixtures: TestFixtures) => Promise<void>;\n /** Full path including describe blocks */\n fullTitle: string;\n /** Whether test is marked with test.only() */\n only: boolean;\n /** Whether test is marked with test.skip() */\n skip: boolean;\n}\n\n/**\n * Holds fixture definitions for a describe scope.\n * Fixtures are inherited from parent scopes and merged with local definitions.\n */\nclass FixtureScope {\n constructor(\n private readonly defs: Map<string, FixtureFunction>,\n private readonly parent: FixtureScope | null = null,\n ) {}\n\n /** Merges all fixture defs from root to this scope */\n getAllDefs(): Map<string, FixtureFunction> {\n const parentDefs = this.parent?.getAllDefs() ?? new Map<string, FixtureFunction>();\n return new Map([...parentDefs, ...this.defs]);\n }\n\n /** Creates a child scope that inherits from this one */\n extend(defs: Map<string, FixtureFunction>): FixtureScope {\n return new FixtureScope(defs, this);\n }\n}\n\n/**\n * A describe block containing tests and hooks.\n */\nexport interface DescribeBlock {\n /** Describe block title */\n title: string;\n /** Tests within this describe */\n tests: TestBlock[];\n /** beforeAll hooks (run once before first test in block) */\n beforeAllHooks?: Array<(fixtures: TestFixtures) => Promise<void>>;\n /** afterAll hooks (run once after last test in block) */\n afterAllHooks?: Array<(fixtures: TestFixtures) => Promise<void>>;\n /** beforeEach hooks (raw — not fixture-wrapped) */\n beforeEachHooks: Array<(fixtures: TestFixtures) => Promise<void>>;\n /** afterEach hooks (raw — not fixture-wrapped) */\n afterEachHooks: Array<(fixtures: TestFixtures) => Promise<void>>;\n /** Nested describe blocks */\n children: DescribeBlock[];\n /** Parent describe block (null for root) */\n parent: DescribeBlock | null;\n /** Fixture scope for this describe block — merged from test.extend() calls */\n fixtureScope: FixtureScope | null;\n}\n\n/**\n * Complete test suite collected from a spec file.\n */\nexport interface TestSuite {\n /** Spec file path or identifier */\n specPath: string;\n /** Root describe block (unnamed) */\n root: DescribeBlock;\n /** Flattened list of all tests in execution order */\n allTests: TestBlock[];\n /** Total test count */\n testCount: number;\n}\n\n/**\n * Global collector state for the current spec file.\n * Reset when a new spec is loaded or after retrieval.\n */\nclass TestCollector {\n private currentSpecPath = '';\n private rootDescribe: DescribeBlock;\n private currentDescribe: DescribeBlock;\n\n constructor() {\n this.rootDescribe = this.createDescribeBlock('', null);\n this.currentDescribe = this.rootDescribe;\n }\n\n private createDescribeBlock(title: string, parent: DescribeBlock | null): DescribeBlock {\n return {\n title,\n tests: [],\n beforeEachHooks: [],\n afterEachHooks: [],\n children: [],\n parent,\n fixtureScope: null,\n };\n }\n\n /**\n * Sets the current spec file path.\n */\n setSpecPath(specPath: string): void {\n this.currentSpecPath = specPath;\n }\n\n /**\n * Resets the collector for a new spec file.\n */\n reset(): void {\n this.currentSpecPath = '';\n this.rootDescribe = this.createDescribeBlock('', null);\n this.currentDescribe = this.rootDescribe;\n }\n\n /**\n * Adds a test to the current describe block.\n * Returns the TestBlock for flag modification (only/skip).\n */\n addTest(title: string, fn: (fixtures: TestFixtures) => Promise<void>, only = false, skip = false): TestBlock {\n const fullTitle = this.getFullTitle(title);\n const testBlock: TestBlock = { title, fn, fullTitle, only, skip };\n this.currentDescribe.tests.push(testBlock);\n return testBlock;\n }\n\n /**\n * Sets or merges fixture definitions on the current describe block.\n * Called by createTestFunction when tests/hooks are registered.\n */\n ensureFixtureScope(defs: Map<string, FixtureFunction>): void {\n if (defs.size === 0) return;\n // Only set once per describe block — subsequent calls with the same\n // or subset defs are no-ops. This prevents duplicate fixture entries\n // when multiple tests/hooks register in the same block.\n if (this.currentDescribe.fixtureScope) return;\n\n // Find parent scope from ancestor describe blocks\n let parentScope: FixtureScope | null = null;\n let ancestor = this.currentDescribe.parent;\n while (ancestor) {\n if (ancestor.fixtureScope) {\n parentScope = ancestor.fixtureScope;\n break;\n }\n ancestor = ancestor.parent;\n }\n this.currentDescribe.fixtureScope = new FixtureScope(defs, parentScope);\n }\n\n /**\n * Enters a new describe block.\n */\n enterDescribe(title: string): void {\n const newDescribe = this.createDescribeBlock(title, this.currentDescribe);\n this.currentDescribe.children.push(newDescribe);\n this.currentDescribe = newDescribe;\n }\n\n /**\n * Exits the current describe block.\n */\n exitDescribe(): void {\n if (this.currentDescribe.parent) {\n this.currentDescribe = this.currentDescribe.parent;\n }\n }\n\n /**\n * Adds a beforeAll hook to the current describe block.\n * In the stub, beforeAll runs once before the first test in the describe.\n */\n addBeforeAll(fn: (fixtures: TestFixtures) => Promise<void>): void {\n if (!this.currentDescribe.beforeAllHooks) {\n this.currentDescribe.beforeAllHooks = [];\n }\n this.currentDescribe.beforeAllHooks.push(fn);\n }\n\n /**\n * Adds an afterAll hook to the current describe block.\n */\n addAfterAll(fn: (fixtures: TestFixtures) => Promise<void>): void {\n if (!this.currentDescribe.afterAllHooks) {\n this.currentDescribe.afterAllHooks = [];\n }\n this.currentDescribe.afterAllHooks.push(fn);\n }\n\n /**\n * Adds a beforeEach hook to the current describe block.\n */\n addBeforeEach(fn: (fixtures: TestFixtures) => Promise<void>): void {\n this.currentDescribe.beforeEachHooks.push(fn);\n }\n\n /**\n * Adds an afterEach hook to the current describe block.\n */\n addAfterEach(fn: (fixtures: TestFixtures) => Promise<void>): void {\n this.currentDescribe.afterEachHooks.push(fn);\n }\n\n /**\n * Marks all tests in the current describe block as skipped.\n * Used when test.skip(true) is called inside a describe block.\n */\n markCurrentDescribeSkipped(): void {\n for (const test of this.currentDescribe.tests) {\n test.skip = true;\n }\n for (const child of this.currentDescribe.children) {\n this.markDescribeSkipped(child);\n }\n }\n\n private markDescribeSkipped(describe: DescribeBlock): void {\n for (const test of describe.tests) {\n test.skip = true;\n }\n for (const child of describe.children) {\n this.markDescribeSkipped(child);\n }\n }\n\n /**\n * Gets the full title path for a test.\n */\n private getFullTitle(testTitle: string): string {\n const parts: string[] = [];\n let current: DescribeBlock | null = this.currentDescribe;\n\n while (current) {\n if (current.title) {\n parts.unshift(current.title);\n }\n current = current.parent;\n }\n\n parts.push(testTitle);\n return parts.join(' > ');\n }\n\n /**\n * Flattens all tests into execution order, including hooks.\n */\n /**\n * Collects fixture defs from the describe block and all ancestors.\n */\n private collectFixtureDefs(describe: DescribeBlock): Map<string, FixtureFunction> {\n return describe.fixtureScope?.getAllDefs() ?? new Map();\n }\n\n private flattenTests(\n describe: DescribeBlock,\n ancestorBeforeEach: Array<(fixtures: TestFixtures) => Promise<void>>,\n ancestorAfterEach: Array<(fixtures: TestFixtures) => Promise<void>>,\n ): TestBlock[] {\n const tests: TestBlock[] = [];\n\n const currentBeforeEach = [...ancestorBeforeEach, ...describe.beforeEachHooks];\n const currentAfterEach = [...describe.afterEachHooks, ...ancestorAfterEach];\n const beforeAllHooks = describe.beforeAllHooks ?? [];\n const afterAllHooks = describe.afterAllHooks ?? [];\n\n // Build ONE fixture wrapper from the describe scope — shared by all tests + hooks\n const fixtureDefs = this.collectFixtureDefs(describe);\n const wrapWithFixtures = createFixtureWrapper(fixtureDefs);\n\n let beforeAllRan = false;\n const allTestsInBlock: TestBlock[] = [];\n\n for (const test of describe.tests) {\n // Combine hooks + test body into one raw function,\n // then wrap with fixtures ONCE. Fixtures resolve once and are\n // shared between beforeAll, beforeEach, test body, and afterEach.\n const combinedFn = async (fixtures: TestFixtures): Promise<void> => {\n if (!beforeAllRan && beforeAllHooks.length > 0) {\n beforeAllRan = true;\n for (const hook of beforeAllHooks) {\n await hook(fixtures);\n }\n }\n\n for (const hook of currentBeforeEach) {\n await hook(fixtures);\n }\n\n await test.fn(fixtures);\n\n for (const hook of currentAfterEach) {\n await hook(fixtures);\n }\n };\n\n allTestsInBlock.push({\n title: test.title,\n fullTitle: test.fullTitle,\n fn: wrapWithFixtures(combinedFn),\n only: test.only,\n skip: test.skip,\n });\n }\n\n for (const child of describe.children) {\n allTestsInBlock.push(...this.flattenTests(child, currentBeforeEach, currentAfterEach));\n }\n\n // Wrap the last test to run afterAll hooks after it completes\n if (afterAllHooks.length > 0 && allTestsInBlock.length > 0) {\n const lastTest = allTestsInBlock[allTestsInBlock.length - 1];\n const originalFn = lastTest.fn;\n lastTest.fn = async (fixtures: TestFixtures): Promise<void> => {\n try {\n await originalFn(fixtures);\n } finally {\n for (const hook of afterAllHooks) {\n await hook(fixtures);\n }\n }\n };\n }\n\n tests.push(...allTestsInBlock);\n return tests;\n }\n\n /**\n * Retrieves the collected test suite and resets for the next spec.\n */\n retrieveAndReset(): TestSuite {\n const allTests = this.flattenTests(this.rootDescribe, [], []);\n\n const suite: TestSuite = {\n specPath: this.currentSpecPath,\n root: this.rootDescribe,\n allTests,\n testCount: allTests.length,\n };\n\n this.reset();\n return suite;\n }\n\n /**\n * Retrieves the collected test suite without resetting.\n * Useful for inspection.\n */\n peek(): TestSuite {\n const allTests = this.flattenTests(this.rootDescribe, [], []);\n\n return {\n specPath: this.currentSpecPath,\n root: this.rootDescribe,\n allTests,\n testCount: allTests.length,\n };\n }\n}\n\n/**\n * Thrown by test.skip() inside a test body to signal the test should be skipped.\n */\nexport class SkipTestError extends Error {\n readonly isSkip = true;\n constructor(reason?: string) {\n super(reason ?? 'Skipped');\n this.name = 'SkipTestError';\n }\n}\n\n/**\n * Global test collector instance.\n * Uses globalThis to ensure the bundled code and SpecRunner share the same collector.\n */\nconst COLLECTOR_KEY = '__playwrightMcpTestCollector__';\nconst globalAny = globalThis as unknown as Record<string, TestCollector>;\nif (!globalAny[COLLECTOR_KEY]) {\n globalAny[COLLECTOR_KEY] = new TestCollector();\n}\nconst collector = globalAny[COLLECTOR_KEY];\n\n/**\n * Test function type that matches Playwright's test() signature.\n */\ntype TestFunction = (fixtures: TestFixtures) => Promise<void>;\n\n/**\n * Fixture definition function following Playwright's async use() pattern.\n * Setup runs before use(), teardown runs after use() returns.\n */\ntype FixtureFunction = (fixtures: Record<string, unknown>, use: (value: unknown) => Promise<void>) => Promise<void>;\n\n/**\n * Parses fixture definitions from the object passed to test.extend().\n * Supports both direct functions and [options, fn] tuple format.\n */\nfunction parseFixtureDefs(fixtures: Record<string, unknown>): Map<string, FixtureFunction> {\n const defs = new Map<string, FixtureFunction>();\n for (const [name, definition] of Object.entries(fixtures)) {\n if (typeof definition === 'function') {\n defs.set(name, definition as FixtureFunction);\n } else if (Array.isArray(definition) && definition.length === 2 && typeof definition[1] === 'function') {\n defs.set(name, definition[1] as FixtureFunction);\n }\n }\n return defs;\n}\n\n/**\n * Wraps a test/hook function to resolve custom fixtures before execution.\n * Uses recursive continuation chaining so each fixture's use() callback\n * scopes the next fixture setup, enabling proper teardown in reverse order.\n */\nfunction createFixtureWrapper(fixtureDefs: Map<string, FixtureFunction>): (fn: TestFunction) => TestFunction {\n if (fixtureDefs.size === 0) return (fn) => fn;\n\n const entries = [...fixtureDefs.entries()];\n\n return (fn: TestFunction): TestFunction => {\n return async (baseFixtures: TestFixtures) => {\n const resolved: TestFixtures = { ...baseFixtures };\n\n async function resolveChain(index: number): Promise<void> {\n if (index >= entries.length) {\n await fn(resolved);\n return;\n }\n\n const [name, setup] = entries[index];\n await setup(resolved, async (value: unknown) => {\n resolved[name] = value;\n await resolveChain(index + 1);\n });\n }\n\n await resolveChain(0);\n };\n };\n}\n\n/**\n * Creates a test function with optional custom fixture definitions.\n * Returned function has the same API surface as Playwright's test().\n */\nfunction createTestFunction(fixtureDefs: Map<string, FixtureFunction>): typeof test {\n // Push fixture defs to the current describe scope whenever a test/hook is registered.\n // This ensures the scope is set even if extend() is called before describe().\n function ensureScope(): void {\n collector.ensureFixtureScope(fixtureDefs);\n }\n\n function extendedTest(title: string, fn: TestFunction): void {\n ensureScope();\n collector.addTest(title, fn);\n }\n\n extendedTest.only = function extendedOnly(title: string, fn: TestFunction): void {\n ensureScope();\n collector.addTest(title, fn, true, false);\n };\n\n extendedTest.skip = function extendedSkip(\n titleOrCondition?: string | boolean,\n fnOrReason?: TestFunction | string,\n ): void {\n if (titleOrCondition === undefined || titleOrCondition === true) {\n throw new SkipTestError(typeof fnOrReason === 'string' ? fnOrReason : 'Skipped');\n }\n if (titleOrCondition === false) return;\n if (typeof titleOrCondition === 'string' && typeof fnOrReason === 'function') {\n ensureScope();\n collector.addTest(titleOrCondition, fnOrReason as TestFunction, false, true);\n }\n };\n\n extendedTest.describe = function extendedDescribe(title: string, fn: () => void): void {\n collector.enterDescribe(title);\n try {\n fn();\n } catch (error) {\n if (error instanceof SkipTestError) {\n collector.markCurrentDescribeSkipped();\n } else {\n throw error;\n }\n }\n collector.exitDescribe();\n };\n\n extendedTest.beforeAll = function extendedBeforeAll(fn: TestFunction): void {\n ensureScope();\n collector.addBeforeAll(fn);\n };\n\n extendedTest.afterAll = function extendedAfterAll(fn: TestFunction): void {\n ensureScope();\n collector.addAfterAll(fn);\n };\n\n extendedTest.beforeEach = function extendedBeforeEach(fn: TestFunction): void {\n ensureScope();\n collector.addBeforeEach(fn);\n };\n\n extendedTest.afterEach = function extendedAfterEach(fn: TestFunction): void {\n ensureScope();\n collector.addAfterEach(fn);\n };\n\n extendedTest.extend = function extendAgain(newFixtures: Record<string, unknown>): typeof test {\n const merged = new Map([...fixtureDefs, ...parseFixtureDefs(newFixtures)]);\n return createTestFunction(merged);\n };\n\n return extendedTest as typeof test;\n}\n\n/**\n * Proxy test function that collects tests instead of executing them.\n * Exported directly with methods attached to match Playwright's API surface.\n */\nexport function test(title: string, fn: TestFunction): void {\n collector.addTest(title, fn);\n}\n\nfunction testOnly(title: string, fn: TestFunction): void {\n collector.addTest(title, fn, true, false);\n}\n\n/**\n * Handles both overloads of test.skip():\n * - test.skip(title, fn) — define a skipped test\n * - test.skip(condition, reason) — skip current test conditionally (inside test body)\n * - test.skip() — unconditionally skip current test (inside test body)\n */\nfunction testSkip(titleOrCondition?: string | boolean, fnOrReason?: TestFunction | string): void {\n // test.skip() or test.skip(true) inside a test body — throw to skip\n if (titleOrCondition === undefined || titleOrCondition === true) {\n throw new SkipTestError(typeof fnOrReason === 'string' ? fnOrReason : 'Skipped');\n }\n // test.skip(false, reason) — don't skip, just continue\n if (titleOrCondition === false) {\n return;\n }\n // test.skip(title, fn) — register a skipped test\n if (typeof titleOrCondition === 'string' && typeof fnOrReason === 'function') {\n collector.addTest(titleOrCondition, fnOrReason as TestFunction, false, true);\n }\n}\n\nfunction describe(title: string, fn: () => void): void {\n collector.enterDescribe(title);\n try {\n fn();\n } catch (error) {\n if (error instanceof SkipTestError) {\n // test.skip(true) inside describe — mark all collected tests in this block as skipped\n collector.markCurrentDescribeSkipped();\n } else {\n throw error;\n }\n }\n collector.exitDescribe();\n}\n\nfunction beforeAll(fn: TestFunction): void {\n collector.addBeforeAll(fn);\n}\n\nfunction afterAll(fn: TestFunction): void {\n collector.addAfterAll(fn);\n}\n\nfunction beforeEach(fn: TestFunction): void {\n collector.addBeforeEach(fn);\n}\n\nfunction afterEach(fn: TestFunction): void {\n collector.addAfterEach(fn);\n}\n\ntest.describe = describe;\ntest.beforeAll = beforeAll;\ntest.afterAll = afterAll;\ntest.beforeEach = beforeEach;\ntest.afterEach = afterEach;\ntest.only = testOnly;\ntest.skip = testSkip;\n\n/**\n * Extends test with custom fixtures.\n * Returns a new test function that resolves fixtures using the use() continuation pattern.\n * Supports chaining: test.extend({...}).extend({...})\n */\ntest.extend = function extend(fixtures: Record<string, unknown>): typeof test {\n return createTestFunction(parseFixtureDefs(fixtures));\n};\n\n/**\n * Initializes the collector for a new spec file.\n * Call this before loading a spec file.\n */\nexport function initCollector(specPath: string): void {\n collector.reset();\n collector.setSpecPath(specPath);\n}\n\n/**\n * Retrieves the collected test suite and resets the collector.\n * Call this after loading a spec file.\n */\nexport function getCollectedSuite(): TestSuite {\n return collector.retrieveAndReset();\n}\n\n/**\n * Peeks at the current collected test suite without resetting.\n */\nexport function peekCollectedSuite(): TestSuite {\n return collector.peek();\n}\n\n/**\n * Resets the collector without retrieving.\n * Useful for cleanup on error.\n */\nexport function resetCollector(): void {\n collector.reset();\n}\n\n/**\n * Wrapper around Playwright's expect that detects proxy objects\n * and routes assertions through extension-mode DOM evaluation.\n * Falls back to real Playwright expect for non-proxy targets.\n * Typed as `typeof playwrightExpect` to preserve Playwright's overloaded signatures.\n */\nexport const expect: typeof playwrightExpect = ((target: unknown) => {\n const obj = target as Record<symbol, boolean>;\n if (obj?.[LOCATOR_PROXY_BRAND] || obj?.[PAGE_PROXY_BRAND]) {\n return createExtensionExpect(target);\n }\n return playwrightExpect(target);\n}) as typeof playwrightExpect;\n"],"mappings":"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"}
@@ -249,5 +249,5 @@ import{createRequire as e}from"node:module";const t=Symbol.for(`__locatorProxyBr
249
249
  }
250
250
 
251
251
  return null;
252
- })()`}};const r=Symbol.for(`__pageProxyBrand__`);async function i(e,t,n=5e3){let r=Date.now();for(;Date.now()-r<n;){if(await e())return;await new Promise(e=>setTimeout(e,100))}throw Error(t)}function a(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e===t}function o(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e.includes(t)}function s(e,t){let n=e=>t?!e:e;return{async toBeVisible(r){await i(async()=>n(await e.isVisible()),t?`Expected element to not be visible`:`Expected element to be visible`,r?.timeout)},async toBeHidden(r){await i(async()=>n(!await e.isVisible()),t?`Expected element to not be hidden`:`Expected element to be hidden`,r?.timeout)},async toBeDisabled(r){await i(async()=>n(!!await e.evaluateProperty(`disabled`)),t?`Expected element to not be disabled`:`Expected element to be disabled`,r?.timeout)},async toBeEnabled(r){await i(async()=>n(!await e.evaluateProperty(`disabled`)),t?`Expected element to not be enabled`:`Expected element to be enabled`,r?.timeout)},async toBeChecked(r){await i(async()=>n(!!await e.evaluateProperty(`checked`)),t?`Expected element to not be checked`:`Expected element to be checked`,r?.timeout)},async toHaveValue(r,o){await i(async()=>n(a(await e.evaluateProperty(`value`),r)),t?`Expected element to not have value "${String(r)}"`:`Expected element to have value "${String(r)}"`,o?.timeout)},async toHaveText(r,o){await i(async()=>n(a((await e.textContent())?.trim()??null,r)),t?`Expected element to not have text "${String(r)}"`:`Expected element to have text "${String(r)}"`,o?.timeout)},async toContainText(r,a){await i(async()=>n(o(await e.textContent(),r)),t?`Expected element to not contain text "${String(r)}"`:`Expected element to contain text "${String(r)}"`,a?.timeout)},async toHaveCount(r,a){await i(async()=>n(await e.count()===r),t?`Expected element count to not be ${r}`:`Expected element count to be ${r}`,a?.timeout)},get not(){return s(e,!t)}}}function c(e,t){let n=e=>t?!e:e;return{async toHaveURL(r,o){await i(async()=>n(a(await e.currentUrlAsync(),r)),t?`Expected page to not have URL "${String(r)}"`:`Expected page to have URL "${String(r)}"`,o?.timeout)},async toHaveTitle(r,o){await i(async()=>n(a(await e.title(),r)),t?`Expected page to not have title "${String(r)}"`:`Expected page to have title "${String(r)}"`,o?.timeout)},get not(){return c(e,!t)}}}function l(e){let n=e;if(n[t])return s(e,!1);if(n[r])return c(e,!1);throw Error(`createExtensionExpect called with unsupported target`)}function u(){try{return e(import.meta.url).resolve(`playwright/test`),import.meta.url}catch{return typeof __PLAYWRIGHT_MCP_STUB_PATH__==`string`?`file://${__PLAYWRIGHT_MCP_STUB_PATH__}`:import.meta.url}}const d=process,f=d.__pw_initiator__;d.__pw_initiator__=void 0;const p=e(u())(`playwright/test`),{expect:m}=p;d.__pw_initiator__=f;const{request:h,chromium:g,firefox:_,webkit:v,devices:y,selectors:b,defineConfig:x,mergeExpects:S,mergeTests:C}=p;var w=class{currentSpecPath=``;rootDescribe;currentDescribe;constructor(){this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}createDescribeBlock(e,t){return{title:e,tests:[],beforeEachHooks:[],afterEachHooks:[],children:[],parent:t}}setSpecPath(e){this.currentSpecPath=e}reset(){this.currentSpecPath=``,this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}addTest(e,t,n=!1,r=!1){let i={title:e,fn:t,fullTitle:this.getFullTitle(e),only:n,skip:r};return this.currentDescribe.tests.push(i),i}enterDescribe(e){let t=this.createDescribeBlock(e,this.currentDescribe);this.currentDescribe.children.push(t),this.currentDescribe=t}exitDescribe(){this.currentDescribe.parent&&(this.currentDescribe=this.currentDescribe.parent)}addBeforeEach(e){this.currentDescribe.beforeEachHooks.push(e)}addAfterEach(e){this.currentDescribe.afterEachHooks.push(e)}markCurrentDescribeSkipped(){for(let e of this.currentDescribe.tests)e.skip=!0;for(let e of this.currentDescribe.children)this.markDescribeSkipped(e)}markDescribeSkipped(e){for(let t of e.tests)t.skip=!0;for(let t of e.children)this.markDescribeSkipped(t)}getFullTitle(e){let t=[],n=this.currentDescribe;for(;n;)n.title&&t.unshift(n.title),n=n.parent;return t.push(e),t.join(` > `)}flattenTests(e,t,n){let r=[],i=[...t,...e.beforeEachHooks],a=[...e.afterEachHooks,...n];for(let t of e.tests)r.push({title:t.title,fullTitle:t.fullTitle,fn:async e=>{for(let t of i)await t(e);await t.fn(e);for(let t of a)await t(e)},only:t.only,skip:t.skip});for(let t of e.children)r.push(...this.flattenTests(t,i,a));return r}retrieveAndReset(){let e=this.flattenTests(this.rootDescribe,[],[]),t={specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length};return this.reset(),t}peek(){let e=this.flattenTests(this.rootDescribe,[],[]);return{specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length}}},T=class extends Error{isSkip=!0;constructor(e){super(e??`Skipped`),this.name=`SkipTestError`}};const E=`__playwrightMcpTestCollector__`,D=globalThis;D[E]||(D[E]=new w);const O=D[E];function k(e){let t=new Map;for(let[n,r]of Object.entries(e))typeof r==`function`?t.set(n,r):Array.isArray(r)&&r.length===2&&typeof r[1]==`function`&&t.set(n,r[1]);return t}function A(e){if(e.size===0)return e=>e;let t=[...e.entries()];return e=>async n=>{let r={...n};async function i(n){if(n>=t.length){await e(r);return}let[a,o]=t[n];await o(r,async e=>{r[a]=e,await i(n+1)})}await i(0)}}function j(e){let t=A(e);function n(e,n){O.addTest(e,t(n))}return n.only=function(e,n){O.addTest(e,t(n),!0,!1)},n.skip=function(e,n){if(e===void 0||e===!0)throw new T(typeof n==`string`?n:`Skipped`);e!==!1&&typeof e==`string`&&typeof n==`function`&&O.addTest(e,t(n),!1,!0)},n.describe=function(e,t){O.enterDescribe(e);try{t()}catch(e){if(e instanceof T)O.markCurrentDescribeSkipped();else throw e}O.exitDescribe()},n.beforeEach=function(e){O.addBeforeEach(t(e))},n.afterEach=function(e){O.addAfterEach(t(e))},n.extend=function(t){return j(new Map([...e,...k(t)]))},n}function M(e,t){O.addTest(e,t)}function N(e,t){O.addTest(e,t,!0,!1)}function P(e,t){if(e===void 0||e===!0)throw new T(typeof t==`string`?t:`Skipped`);e!==!1&&typeof e==`string`&&typeof t==`function`&&O.addTest(e,t,!1,!0)}function F(e,t){O.enterDescribe(e);try{t()}catch(e){if(e instanceof T)O.markCurrentDescribeSkipped();else throw e}O.exitDescribe()}function I(e){O.addBeforeEach(e)}function L(e){O.addAfterEach(e)}M.describe=F,M.beforeEach=I,M.afterEach=L,M.only=N,M.skip=P,M.extend=function(e){return j(k(e))};function R(e){O.reset(),O.setSpecPath(e)}function z(){return O.retrieveAndReset()}function B(){return O.peek()}function V(){O.reset()}const H=(e=>{let n=e;return n?.[t]||n?.[r]?l(e):m(e)});export{r as _,H as a,R as c,B as d,h as f,v as g,M as h,y as i,S as l,b as m,g as n,_ as o,V as p,x as r,z as s,T as t,C as u,n as v};
253
- //# sourceMappingURL=playwright-test-CjR5XFNZ.mjs.map
252
+ })()`}};const r=Symbol.for(`__pageProxyBrand__`);async function i(e,t,n=5e3){let r=Date.now();for(;Date.now()-r<n;){if(await e())return;await new Promise(e=>setTimeout(e,100))}throw Error(t)}function a(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e===t}function o(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e.includes(t)}function s(e,t){let n=e=>t?!e:e;return{async toBeVisible(r){await i(async()=>n(await e.isVisible()),t?`Expected element to not be visible`:`Expected element to be visible`,r?.timeout)},async toBeHidden(r){await i(async()=>n(!await e.isVisible()),t?`Expected element to not be hidden`:`Expected element to be hidden`,r?.timeout)},async toBeDisabled(r){await i(async()=>n(!!await e.evaluateProperty(`disabled`)),t?`Expected element to not be disabled`:`Expected element to be disabled`,r?.timeout)},async toBeEnabled(r){await i(async()=>n(!await e.evaluateProperty(`disabled`)),t?`Expected element to not be enabled`:`Expected element to be enabled`,r?.timeout)},async toBeChecked(r){await i(async()=>n(!!await e.evaluateProperty(`checked`)),t?`Expected element to not be checked`:`Expected element to be checked`,r?.timeout)},async toHaveValue(r,o){await i(async()=>n(a(await e.evaluateProperty(`value`),r)),t?`Expected element to not have value "${String(r)}"`:`Expected element to have value "${String(r)}"`,o?.timeout)},async toHaveText(r,o){await i(async()=>n(a((await e.textContent())?.trim()??null,r)),t?`Expected element to not have text "${String(r)}"`:`Expected element to have text "${String(r)}"`,o?.timeout)},async toContainText(r,a){await i(async()=>n(o(await e.textContent(),r)),t?`Expected element to not contain text "${String(r)}"`:`Expected element to contain text "${String(r)}"`,a?.timeout)},async toHaveCount(r,a){await i(async()=>n(await e.count()===r),t?`Expected element count to not be ${r}`:`Expected element count to be ${r}`,a?.timeout)},get not(){return s(e,!t)}}}function c(e,t){let n=e=>t?!e:e;return{async toHaveURL(r,o){await i(async()=>n(a(await e.currentUrlAsync(),r)),t?`Expected page to not have URL "${String(r)}"`:`Expected page to have URL "${String(r)}"`,o?.timeout)},async toHaveTitle(r,o){await i(async()=>n(a(await e.title(),r)),t?`Expected page to not have title "${String(r)}"`:`Expected page to have title "${String(r)}"`,o?.timeout)},get not(){return c(e,!t)}}}function l(e){let n=e;if(n[t])return s(e,!1);if(n[r])return c(e,!1);throw Error(`createExtensionExpect called with unsupported target`)}function u(){try{return e(import.meta.url).resolve(`playwright/test`),import.meta.url}catch{return typeof __PLAYWRIGHT_MCP_STUB_PATH__==`string`?`file://${__PLAYWRIGHT_MCP_STUB_PATH__}`:import.meta.url}}const d=process,f=d.__pw_initiator__;d.__pw_initiator__=void 0;const p=e(u())(`playwright/test`),{expect:m}=p;d.__pw_initiator__=f;const{request:h,chromium:g,firefox:_,webkit:v,devices:y,selectors:b,defineConfig:x,mergeExpects:S,mergeTests:C}=p;var w=class e{constructor(e,t=null){this.defs=e,this.parent=t}getAllDefs(){let e=this.parent?.getAllDefs()??new Map;return new Map([...e,...this.defs])}extend(t){return new e(t,this)}},T=class{currentSpecPath=``;rootDescribe;currentDescribe;constructor(){this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}createDescribeBlock(e,t){return{title:e,tests:[],beforeEachHooks:[],afterEachHooks:[],children:[],parent:t,fixtureScope:null}}setSpecPath(e){this.currentSpecPath=e}reset(){this.currentSpecPath=``,this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}addTest(e,t,n=!1,r=!1){let i={title:e,fn:t,fullTitle:this.getFullTitle(e),only:n,skip:r};return this.currentDescribe.tests.push(i),i}ensureFixtureScope(e){if(e.size===0||this.currentDescribe.fixtureScope)return;let t=null,n=this.currentDescribe.parent;for(;n;){if(n.fixtureScope){t=n.fixtureScope;break}n=n.parent}this.currentDescribe.fixtureScope=new w(e,t)}enterDescribe(e){let t=this.createDescribeBlock(e,this.currentDescribe);this.currentDescribe.children.push(t),this.currentDescribe=t}exitDescribe(){this.currentDescribe.parent&&(this.currentDescribe=this.currentDescribe.parent)}addBeforeAll(e){this.currentDescribe.beforeAllHooks||(this.currentDescribe.beforeAllHooks=[]),this.currentDescribe.beforeAllHooks.push(e)}addAfterAll(e){this.currentDescribe.afterAllHooks||(this.currentDescribe.afterAllHooks=[]),this.currentDescribe.afterAllHooks.push(e)}addBeforeEach(e){this.currentDescribe.beforeEachHooks.push(e)}addAfterEach(e){this.currentDescribe.afterEachHooks.push(e)}markCurrentDescribeSkipped(){for(let e of this.currentDescribe.tests)e.skip=!0;for(let e of this.currentDescribe.children)this.markDescribeSkipped(e)}markDescribeSkipped(e){for(let t of e.tests)t.skip=!0;for(let t of e.children)this.markDescribeSkipped(t)}getFullTitle(e){let t=[],n=this.currentDescribe;for(;n;)n.title&&t.unshift(n.title),n=n.parent;return t.push(e),t.join(` > `)}collectFixtureDefs(e){return e.fixtureScope?.getAllDefs()??new Map}flattenTests(e,t,n){let r=[],i=[...t,...e.beforeEachHooks],a=[...e.afterEachHooks,...n],o=e.beforeAllHooks??[],s=e.afterAllHooks??[],c=j(this.collectFixtureDefs(e)),l=!1,u=[];for(let t of e.tests)u.push({title:t.title,fullTitle:t.fullTitle,fn:c(async e=>{if(!l&&o.length>0){l=!0;for(let t of o)await t(e)}for(let t of i)await t(e);await t.fn(e);for(let t of a)await t(e)}),only:t.only,skip:t.skip});for(let t of e.children)u.push(...this.flattenTests(t,i,a));if(s.length>0&&u.length>0){let e=u[u.length-1],t=e.fn;e.fn=async e=>{try{await t(e)}finally{for(let t of s)await t(e)}}}return r.push(...u),r}retrieveAndReset(){let e=this.flattenTests(this.rootDescribe,[],[]),t={specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length};return this.reset(),t}peek(){let e=this.flattenTests(this.rootDescribe,[],[]);return{specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length}}},E=class extends Error{isSkip=!0;constructor(e){super(e??`Skipped`),this.name=`SkipTestError`}};const D=`__playwrightMcpTestCollector__`,O=globalThis;O[D]||(O[D]=new T);const k=O[D];function A(e){let t=new Map;for(let[n,r]of Object.entries(e))typeof r==`function`?t.set(n,r):Array.isArray(r)&&r.length===2&&typeof r[1]==`function`&&t.set(n,r[1]);return t}function j(e){if(e.size===0)return e=>e;let t=[...e.entries()];return e=>async n=>{let r={...n};async function i(n){if(n>=t.length){await e(r);return}let[a,o]=t[n];await o(r,async e=>{r[a]=e,await i(n+1)})}await i(0)}}function M(e){function t(){k.ensureFixtureScope(e)}function n(e,n){t(),k.addTest(e,n)}return n.only=function(e,n){t(),k.addTest(e,n,!0,!1)},n.skip=function(e,n){if(e===void 0||e===!0)throw new E(typeof n==`string`?n:`Skipped`);e!==!1&&typeof e==`string`&&typeof n==`function`&&(t(),k.addTest(e,n,!1,!0))},n.describe=function(e,t){k.enterDescribe(e);try{t()}catch(e){if(e instanceof E)k.markCurrentDescribeSkipped();else throw e}k.exitDescribe()},n.beforeAll=function(e){t(),k.addBeforeAll(e)},n.afterAll=function(e){t(),k.addAfterAll(e)},n.beforeEach=function(e){t(),k.addBeforeEach(e)},n.afterEach=function(e){t(),k.addAfterEach(e)},n.extend=function(t){return M(new Map([...e,...A(t)]))},n}function N(e,t){k.addTest(e,t)}function P(e,t){k.addTest(e,t,!0,!1)}function F(e,t){if(e===void 0||e===!0)throw new E(typeof t==`string`?t:`Skipped`);e!==!1&&typeof e==`string`&&typeof t==`function`&&k.addTest(e,t,!1,!0)}function I(e,t){k.enterDescribe(e);try{t()}catch(e){if(e instanceof E)k.markCurrentDescribeSkipped();else throw e}k.exitDescribe()}function L(e){k.addBeforeAll(e)}function R(e){k.addAfterAll(e)}function z(e){k.addBeforeEach(e)}function B(e){k.addAfterEach(e)}N.describe=I,N.beforeAll=L,N.afterAll=R,N.beforeEach=z,N.afterEach=B,N.only=P,N.skip=F,N.extend=function(e){return M(A(e))};function V(e){k.reset(),k.setSpecPath(e)}function H(){return k.retrieveAndReset()}function U(){return k.peek()}function W(){k.reset()}const G=(e=>{let n=e;return n?.[t]||n?.[r]?l(e):m(e)});export{r as _,G as a,V as c,U as d,h as f,v as g,N as h,y as i,S as l,b as m,g as n,_ as o,W as p,x as r,H as s,E as t,C as u,n as v};
253
+ //# sourceMappingURL=playwright-test-yNsP599T.mjs.map