feat: allow console / network on pause (#37593)
This commit is contained in:
		
							parent
							
								
									888da0f89d
								
							
						
					
					
						commit
						4112695fab
					
				|  | @ -1,6 +1,6 @@ | ||||||
| --- | --- | ||||||
| name: playwright-test-generator | name: playwright-test-generator | ||||||
| description: Use this agent when you need to create automated browser tests using Playwright. Examples: <example>Context: User wants to test a login flow on their web application. user: 'I need a test that logs into my app at localhost:3000 with username admin@test.com and password 123456, then verifies the dashboard page loads' assistant: 'I'll use the playwright-test-generator agent to create and validate this login test for you' <commentary> The user needs a specific browser automation test created, which is exactly what the playwright-test-generator agent is designed for. </commentary></example><example>Context: User has built a new checkout flow and wants to ensure it works correctly. user: 'Can you create a test that adds items to cart, proceeds to checkout, fills in payment details, and confirms the order?' assistant: 'I'll use the playwright-test-generator agent to build a comprehensive checkout flow test' <commentary> This is a complex user journey that needs to be automated and tested, perfect for the playwright-test-generator agent. </commentary></example> | description: Use this agent when you need to create automated browser tests using Playwright. Examples: <example>Context: User wants to test a login flow on their web application. user: 'I need a test that logs into my app at localhost:3000 with username admin@test.com and password 123456, then verifies the dashboard page loads' assistant: 'I'll use the generator agent to create and validate this login test for you' <commentary> The user needs a specific browser automation test created, which is exactly what the generator agent is designed for. </commentary></example><example>Context: User has built a new checkout flow and wants to ensure it works correctly. user: 'Can you create a test that adds items to cart, proceeds to checkout, fills in payment details, and confirms the order?' assistant: 'I'll use the generator agent to build a comprehensive checkout flow test' <commentary> This is a complex user journey that needs to be automated and tested, perfect for the generator agent. </commentary></example> | ||||||
| tools: Glob, Grep, Read, Write, mcp__playwright-test__browser_click, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_verify_element_visible, mcp__playwright-test__browser_verify_list_visible, mcp__playwright-test__browser_verify_text_visible, mcp__playwright-test__browser_verify_value, mcp__playwright-test__browser_wait_for, mcp__playwright-test__test_setup_page | tools: Glob, Grep, Read, Write, mcp__playwright-test__browser_click, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_verify_element_visible, mcp__playwright-test__browser_verify_list_visible, mcp__playwright-test__browser_verify_text_visible, mcp__playwright-test__browser_verify_value, mcp__playwright-test__browser_wait_for, mcp__playwright-test__test_setup_page | ||||||
| model: sonnet | model: sonnet | ||||||
| color: blue | color: blue | ||||||
|  | @ -30,6 +30,7 @@ Your process is methodical and thorough: | ||||||
|    @playwright/test source code that follows following convention: |    @playwright/test source code that follows following convention: | ||||||
| 
 | 
 | ||||||
|    - One file per scenario, one test in a file |    - One file per scenario, one test in a file | ||||||
|  |    - Use seed test content (copyright, structure) to emit consistent tests. | ||||||
|    - File name must be fs-friendly scenario name |    - File name must be fs-friendly scenario name | ||||||
|    - Test must be placed in a describe matching the top-level test plan item |    - Test must be placed in a describe matching the top-level test plan item | ||||||
|    - Test title must match the scenario name |    - Test title must match the scenario name | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| --- | --- | ||||||
| name: playwright-test-healer | name: playwright-test-healer | ||||||
| description: Use this agent when you need to debug and fix failing Playwright tests. Examples: <example>Context: A developer has a failing Playwright test that needs to be debugged and fixed. user: 'The login test is failing, can you fix it?' assistant: 'I'll use the playwright-test-healer agent to debug and fix the failing login test.' <commentary> The user has identified a specific failing test that needs debugging and fixing, which is exactly what the playwright-test-healer agent is designed for. </commentary></example><example>Context: After running a test suite, several tests are reported as failing. user: 'Test user-registration.spec.ts is broken after the recent changes' assistant: 'Let me use the playwright-test-healer agent to investigate and fix the user-registration test.' <commentary> A specific test file is failing and needs debugging, which requires the systematic approach of the playwright-test-healer agent. </commentary></example> | description: Use this agent when you need to debug and fix failing Playwright tests. Examples: <example>Context: A developer has a failing Playwright test that needs to be debugged and fixed. user: 'The login test is failing, can you fix it?' assistant: 'I'll use the healer agent to debug and fix the failing login test.' <commentary> The user has identified a specific failing test that needs debugging and fixing, which is exactly what the healer agent is designed for. </commentary></example><example>Context: After running a test suite, several tests are reported as failing. user: 'Test user-registration.spec.ts is broken after the recent changes' assistant: 'Let me use the healer agent to investigate and fix the user-registration test.' <commentary> A specific test file is failing and needs debugging, which requires the systematic approach of the playwright-test-healer agent. </commentary></example> | ||||||
| tools: Glob, Grep, Read, Write, Edit, MultiEdit, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_generate_locator, mcp__playwright-test__browser_snapshot, mcp__playwright-test__test_debug, mcp__playwright-test__test_list, mcp__playwright-test__test_run | tools: Glob, Grep, Read, Write, Edit, MultiEdit, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_generate_locator, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_snapshot, mcp__playwright-test__test_debug, mcp__playwright-test__test_list, mcp__playwright-test__test_run | ||||||
| model: sonnet | model: sonnet | ||||||
| color: red | color: red | ||||||
| --- | --- | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| --- | --- | ||||||
| name: playwright-test-planner | name: playwright-test-planner | ||||||
| description: Use this agent when you need to create comprehensive test plan for a web application or website. Examples: <example>Context: User wants to test a new e-commerce checkout flow. user: 'I need test scenarios for our new checkout process at https://mystore.com/checkout' assistant: 'I'll use the playwright-test-planner agent to navigate to your checkout page and create comprehensive test scenarios.' <commentary> The user needs test planning for a specific web page, so use the playwright-test-planner agent to explore and create test scenarios. </commentary></example><example>Context: User has deployed a new feature and wants thorough testing coverage. user: 'Can you help me test our new user dashboard at https://app.example.com/dashboard?' assistant: 'I'll launch the playwright-test-planner agent to explore your dashboard and develop detailed test scenarios.' <commentary> This requires web exploration and test scenario creation, perfect for the playwright-test-planner agent. </commentary></example> | description: Use this agent when you need to create comprehensive test plan for a web application or website. Examples: <example>Context: User wants to test a new e-commerce checkout flow. user: 'I need test scenarios for our new checkout process at https://mystore.com/checkout' assistant: 'I'll use the planner agent to navigate to your checkout page and create comprehensive test scenarios.' <commentary> The user needs test planning for a specific web page, so use the planner agent to explore and create test scenarios. </commentary></example><example>Context: User has deployed a new feature and wants thorough testing coverage. user: 'Can you help me test our new user dashboard at https://app.example.com/dashboard?' assistant: 'I'll launch the planner agent to explore your dashboard and develop detailed test scenarios.' <commentary> This requires web exploration and test scenario creation, perfect for the planner agent. </commentary></example> | ||||||
| tools: Glob, Grep, Read, Write, mcp__playwright-test__browser_click, mcp__playwright-test__browser_close, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_navigate_back, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_take_screenshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_wait_for, mcp__playwright-test__test_setup_page | tools: Glob, Grep, Read, Write, mcp__playwright-test__browser_click, mcp__playwright-test__browser_close, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_navigate_back, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_take_screenshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_wait_for, mcp__playwright-test__test_setup_page | ||||||
| model: sonnet | model: sonnet | ||||||
| color: green | color: green | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ Your process is methodical and thorough: | ||||||
|    @playwright/test source code that follows following convention: |    @playwright/test source code that follows following convention: | ||||||
| 
 | 
 | ||||||
|    - One file per scenario, one test in a file |    - One file per scenario, one test in a file | ||||||
|  |    - Use seed test content (copyright, structure) to emit consistent tests. | ||||||
|    - File name must be fs-friendly scenario name |    - File name must be fs-friendly scenario name | ||||||
|    - Test must be placed in a describe matching the top-level test plan item |    - Test must be placed in a describe matching the top-level test plan item | ||||||
|    - Test title must match the scenario name |    - Test title must match the scenario name | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| --- | --- | ||||||
| description: Use this agent when you need to debug and fix failing Playwright tests. | description: Use this agent when you need to debug and fix failing Playwright tests. | ||||||
| tools: ['createFile', 'createDirectory', 'editFiles', 'fileSearch', 'textSearch', 'listDirectory', 'readFile', 'test_browser_evaluate', 'test_browser_generate_locator', 'test_browser_snapshot', 'test_debug', 'test_list', 'test_run'] | tools: ['createFile', 'createDirectory', 'editFiles', 'fileSearch', 'textSearch', 'listDirectory', 'readFile', 'test_browser_console_messages', 'test_browser_evaluate', 'test_browser_generate_locator', 'test_browser_network_requests', 'test_browser_snapshot', 'test_debug', 'test_list', 'test_run'] | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and | You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and | ||||||
|  |  | ||||||
|  | @ -91,6 +91,7 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap | ||||||
|   private _actualHeadersPromise: Promise<RawHeaders> | undefined; |   private _actualHeadersPromise: Promise<RawHeaders> | undefined; | ||||||
|   _timing: ResourceTiming; |   _timing: ResourceTiming; | ||||||
|   private _fallbackOverrides: SerializedFallbackOverrides = {}; |   private _fallbackOverrides: SerializedFallbackOverrides = {}; | ||||||
|  |   _hasResponse = false; | ||||||
| 
 | 
 | ||||||
|   static from(request: channels.RequestChannel): Request { |   static from(request: channels.RequestChannel): Request { | ||||||
|     return (request as any)._object; |     return (request as any)._object; | ||||||
|  | @ -117,6 +118,8 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap | ||||||
|       responseStart: -1, |       responseStart: -1, | ||||||
|       responseEnd: -1, |       responseEnd: -1, | ||||||
|     }; |     }; | ||||||
|  |     this._hasResponse = this._initializer.hasResponse; | ||||||
|  |     this._channel.on('response', () => this._hasResponse = true); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   url(): string { |   url(): string { | ||||||
|  |  | ||||||
|  | @ -2266,7 +2266,9 @@ scheme.RequestInitializer = tObject({ | ||||||
|   headers: tArray(tType('NameValue')), |   headers: tArray(tType('NameValue')), | ||||||
|   isNavigationRequest: tBoolean, |   isNavigationRequest: tBoolean, | ||||||
|   redirectedFrom: tOptional(tChannel(['Request'])), |   redirectedFrom: tOptional(tChannel(['Request'])), | ||||||
|  |   hasResponse: tBoolean, | ||||||
| }); | }); | ||||||
|  | scheme.RequestResponseEvent = tOptional(tObject({})); | ||||||
| scheme.RequestResponseParams = tOptional(tObject({})); | scheme.RequestResponseParams = tOptional(tObject({})); | ||||||
| scheme.RequestResponseResult = tObject({ | scheme.RequestResponseResult = tObject({ | ||||||
|   response: tOptional(tChannel(['Response'])), |   response: tOptional(tChannel(['Response'])), | ||||||
|  |  | ||||||
|  | @ -19,16 +19,16 @@ import { Dispatcher } from './dispatcher'; | ||||||
| import { FrameDispatcher } from './frameDispatcher'; | import { FrameDispatcher } from './frameDispatcher'; | ||||||
| import { WorkerDispatcher } from './pageDispatcher'; | import { WorkerDispatcher } from './pageDispatcher'; | ||||||
| import { TracingDispatcher } from './tracingDispatcher'; | import { TracingDispatcher } from './tracingDispatcher'; | ||||||
|  | import { Request } from '../network'; | ||||||
| 
 | 
 | ||||||
| import type { APIRequestContext } from '../fetch'; | import type { APIRequestContext } from '../fetch'; | ||||||
| import type { Request, Response, Route } from '../network'; | import type { Response, Route } from '../network'; | ||||||
| import type { BrowserContextDispatcher } from './browserContextDispatcher'; | import type { BrowserContextDispatcher } from './browserContextDispatcher'; | ||||||
| import type { RootDispatcher } from './dispatcher'; | import type { RootDispatcher } from './dispatcher'; | ||||||
| import type { PageDispatcher } from './pageDispatcher'; | import type { PageDispatcher } from './pageDispatcher'; | ||||||
| import type * as channels from '@protocol/channels'; | import type * as channels from '@protocol/channels'; | ||||||
| import type { Progress } from '@protocol/progress'; | import type { Progress } from '@protocol/progress'; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| export class RequestDispatcher extends Dispatcher<Request, channels.RequestChannel, BrowserContextDispatcher | PageDispatcher | FrameDispatcher> implements channels.RequestChannel { | export class RequestDispatcher extends Dispatcher<Request, channels.RequestChannel, BrowserContextDispatcher | PageDispatcher | FrameDispatcher> implements channels.RequestChannel { | ||||||
|   _type_Request: boolean; |   _type_Request: boolean; | ||||||
|   private _browserContextDispatcher: BrowserContextDispatcher; |   private _browserContextDispatcher: BrowserContextDispatcher; | ||||||
|  | @ -60,9 +60,11 @@ export class RequestDispatcher extends Dispatcher<Request, channels.RequestChann | ||||||
|       headers: request.headers(), |       headers: request.headers(), | ||||||
|       isNavigationRequest: request.isNavigationRequest(), |       isNavigationRequest: request.isNavigationRequest(), | ||||||
|       redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()), |       redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()), | ||||||
|  |       hasResponse: !!request._existingResponse(), | ||||||
|     }); |     }); | ||||||
|     this._type_Request = true; |     this._type_Request = true; | ||||||
|     this._browserContextDispatcher = scope; |     this._browserContextDispatcher = scope; | ||||||
|  |     this.addObjectListener(Request.Events.Response, () => this._dispatchEvent('response', {})); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async rawRequestHeaders(params: channels.RequestRawRequestHeadersParams, progress: Progress): Promise<channels.RequestRawRequestHeadersResult> { |   async rawRequestHeaders(params: channels.RequestRawRequestHeadersParams, progress: Progress): Promise<channels.RequestRawRequestHeadersResult> { | ||||||
|  |  | ||||||
|  | @ -117,6 +117,10 @@ export class Request extends SdkObject { | ||||||
|   private _bodySize: number | undefined; |   private _bodySize: number | undefined; | ||||||
|   _responseBodyOverride: { body: string; isBase64: boolean; } | undefined; |   _responseBodyOverride: { body: string; isBase64: boolean; } | undefined; | ||||||
| 
 | 
 | ||||||
|  |   static Events = { | ||||||
|  |     Response: 'response', | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   constructor(context: contexts.BrowserContext, frame: frames.Frame | null, serviceWorker: pages.Worker | null, redirectedFrom: Request | null, documentId: string | undefined, |   constructor(context: contexts.BrowserContext, frame: frames.Frame | null, serviceWorker: pages.Worker | null, redirectedFrom: Request | null, documentId: string | undefined, | ||||||
|     url: string, resourceType: string, method: string, postData: Buffer | null, headers: HeadersArray) { |     url: string, resourceType: string, method: string, postData: Buffer | null, headers: HeadersArray) { | ||||||
|     super(frame || context, 'request'); |     super(frame || context, 'request'); | ||||||
|  | @ -202,6 +206,7 @@ export class Request extends SdkObject { | ||||||
|   _setResponse(response: Response) { |   _setResponse(response: Response) { | ||||||
|     this._response = response; |     this._response = response; | ||||||
|     this._waitForResponsePromise.resolve(response); |     this._waitForResponsePromise.resolve(response); | ||||||
|  |     this.emit(Request.Events.Response, response); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _finalRequest(): Request { |   _finalRequest(): Request { | ||||||
|  |  | ||||||
|  | @ -9,8 +9,10 @@ tools: | ||||||
|   - read |   - read | ||||||
|   - write |   - write | ||||||
|   - edit |   - edit | ||||||
|  |   - playwright-test/browser_console_messages | ||||||
|   - playwright-test/browser_evaluate |   - playwright-test/browser_evaluate | ||||||
|   - playwright-test/browser_generate_locator |   - playwright-test/browser_generate_locator | ||||||
|  |   - playwright-test/browser_network_requests | ||||||
|   - playwright-test/browser_snapshot |   - playwright-test/browser_snapshot | ||||||
|   - playwright-test/test_debug |   - playwright-test/test_debug | ||||||
|   - playwright-test/test_list |   - playwright-test/test_list | ||||||
|  |  | ||||||
|  | @ -53,10 +53,11 @@ export class Tab extends EventEmitter<TabEventsInterface> { | ||||||
|   private _lastTitle = 'about:blank'; |   private _lastTitle = 'about:blank'; | ||||||
|   private _consoleMessages: ConsoleMessage[] = []; |   private _consoleMessages: ConsoleMessage[] = []; | ||||||
|   private _recentConsoleMessages: ConsoleMessage[] = []; |   private _recentConsoleMessages: ConsoleMessage[] = []; | ||||||
|   private _requests: Map<playwright.Request, playwright.Response | null> = new Map(); |   private _requests: Set<playwright.Request> = new Set(); | ||||||
|   private _onPageClose: (tab: Tab) => void; |   private _onPageClose: (tab: Tab) => void; | ||||||
|   private _modalStates: ModalState[] = []; |   private _modalStates: ModalState[] = []; | ||||||
|   private _downloads: { download: playwright.Download, finished: boolean, outputFile: string }[] = []; |   private _downloads: { download: playwright.Download, finished: boolean, outputFile: string }[] = []; | ||||||
|  |   private _initializedPromise: Promise<void>; | ||||||
| 
 | 
 | ||||||
|   constructor(context: Context, page: playwright.Page, onPageClose: (tab: Tab) => void) { |   constructor(context: Context, page: playwright.Page, onPageClose: (tab: Tab) => void) { | ||||||
|     super(); |     super(); | ||||||
|  | @ -65,8 +66,7 @@ export class Tab extends EventEmitter<TabEventsInterface> { | ||||||
|     this._onPageClose = onPageClose; |     this._onPageClose = onPageClose; | ||||||
|     page.on('console', event => this._handleConsoleMessage(messageToConsoleMessage(event))); |     page.on('console', event => this._handleConsoleMessage(messageToConsoleMessage(event))); | ||||||
|     page.on('pageerror', error => this._handleConsoleMessage(pageErrorToConsoleMessage(error))); |     page.on('pageerror', error => this._handleConsoleMessage(pageErrorToConsoleMessage(error))); | ||||||
|     page.on('request', request => this._requests.set(request, null)); |     page.on('request', request => this._requests.add(request)); | ||||||
|     page.on('response', response => this._requests.set(response.request(), response)); |  | ||||||
|     page.on('close', () => this._onClose()); |     page.on('close', () => this._onClose()); | ||||||
|     page.on('filechooser', chooser => { |     page.on('filechooser', chooser => { | ||||||
|       this.setModalState({ |       this.setModalState({ | ||||||
|  | @ -83,7 +83,7 @@ export class Tab extends EventEmitter<TabEventsInterface> { | ||||||
|     page.setDefaultNavigationTimeout(this.context.config.timeouts.navigation); |     page.setDefaultNavigationTimeout(this.context.config.timeouts.navigation); | ||||||
|     page.setDefaultTimeout(this.context.config.timeouts.action); |     page.setDefaultTimeout(this.context.config.timeouts.action); | ||||||
|     (page as any)[tabSymbol] = this; |     (page as any)[tabSymbol] = this; | ||||||
|     void this._initialize(); |     this._initializedPromise = this._initialize(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static forPage(page: playwright.Page): Tab | undefined { |   static forPage(page: playwright.Page): Tab | undefined { | ||||||
|  | @ -98,13 +98,8 @@ export class Tab extends EventEmitter<TabEventsInterface> { | ||||||
|     for (const error of errors) |     for (const error of errors) | ||||||
|       this._handleConsoleMessage(pageErrorToConsoleMessage(error)); |       this._handleConsoleMessage(pageErrorToConsoleMessage(error)); | ||||||
|     const requests = await this.page.requests().catch(() => []); |     const requests = await this.page.requests().catch(() => []); | ||||||
|     for (const request of requests) { |     for (const request of requests) | ||||||
|       this._requests.set(request, null); |       this._requests.add(request); | ||||||
|       void request.response().catch(() => null).then(response => { |  | ||||||
|         if (response) |  | ||||||
|           this._requests.set(request, response); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   modalStates(): ModalState[] { |   modalStates(): ModalState[] { | ||||||
|  | @ -207,11 +202,13 @@ export class Tab extends EventEmitter<TabEventsInterface> { | ||||||
|     await this.waitForLoadState('load', { timeout: 5000 }); |     await this.waitForLoadState('load', { timeout: 5000 }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   consoleMessages(): ConsoleMessage[] { |   async consoleMessages(): Promise<ConsoleMessage[]> { | ||||||
|  |     await this._initializedPromise; | ||||||
|     return this._consoleMessages; |     return this._consoleMessages; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   requests(): Map<playwright.Request, playwright.Response | null> { |   async requests(): Promise<Set<playwright.Request>> { | ||||||
|  |     await this._initializedPromise; | ||||||
|     return this._requests; |     return this._requests; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,7 +27,8 @@ const console = defineTabTool({ | ||||||
|     type: 'readOnly', |     type: 'readOnly', | ||||||
|   }, |   }, | ||||||
|   handle: async (tab, params, response) => { |   handle: async (tab, params, response) => { | ||||||
|     tab.consoleMessages().map(message => response.addResult(message.toString())); |     const messages = await tab.consoleMessages(); | ||||||
|  |     messages.map(message => response.addResult(message.toString())); | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ import { z } from '../../sdk/bundle'; | ||||||
| import { defineTabTool } from './tool'; | import { defineTabTool } from './tool'; | ||||||
| 
 | 
 | ||||||
| import type * as playwright from 'playwright-core'; | import type * as playwright from 'playwright-core'; | ||||||
|  | import type { Request } from '../../../../../playwright-core/src/client/network'; | ||||||
| 
 | 
 | ||||||
| const requests = defineTabTool({ | const requests = defineTabTool({ | ||||||
|   capability: 'core', |   capability: 'core', | ||||||
|  | @ -31,16 +32,21 @@ const requests = defineTabTool({ | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   handle: async (tab, params, response) => { |   handle: async (tab, params, response) => { | ||||||
|     const requests = tab.requests(); |     const requests = await tab.requests(); | ||||||
|     [...requests.entries()].forEach(([req, res]) => response.addResult(renderRequest(req, res))); |     for (const request of requests) | ||||||
|  |       response.addResult(await renderRequest(request)); | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| function renderRequest(request: playwright.Request, response: playwright.Response | null) { | async function renderRequest(request: playwright.Request) { | ||||||
|   const result: string[] = []; |   const result: string[] = []; | ||||||
|   result.push(`[${request.method().toUpperCase()}] ${request.url()}`); |   result.push(`[${request.method().toUpperCase()}] ${request.url()}`); | ||||||
|  |   const hasResponse = (request as Request)._hasResponse; | ||||||
|  |   if (hasResponse) { | ||||||
|  |     const response = await request.response(); | ||||||
|     if (response) |     if (response) | ||||||
|       result.push(`=> [${response.status()}] ${response.statusText()}`); |       result.push(`=> [${response.status()}] ${response.statusText()}`); | ||||||
|  |   } | ||||||
|   return result.join(' '); |   return result.join(' '); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3863,14 +3863,17 @@ export type RequestInitializer = { | ||||||
|   headers: NameValue[], |   headers: NameValue[], | ||||||
|   isNavigationRequest: boolean, |   isNavigationRequest: boolean, | ||||||
|   redirectedFrom?: RequestChannel, |   redirectedFrom?: RequestChannel, | ||||||
|  |   hasResponse: boolean, | ||||||
| }; | }; | ||||||
| export interface RequestEventTarget { | export interface RequestEventTarget { | ||||||
|  |   on(event: 'response', callback: (params: RequestResponseEvent) => void): this; | ||||||
| } | } | ||||||
| export interface RequestChannel extends RequestEventTarget, Channel { | export interface RequestChannel extends RequestEventTarget, Channel { | ||||||
|   _type_Request: boolean; |   _type_Request: boolean; | ||||||
|   response(params?: RequestResponseParams, progress?: Progress): Promise<RequestResponseResult>; |   response(params?: RequestResponseParams, progress?: Progress): Promise<RequestResponseResult>; | ||||||
|   rawRequestHeaders(params?: RequestRawRequestHeadersParams, progress?: Progress): Promise<RequestRawRequestHeadersResult>; |   rawRequestHeaders(params?: RequestRawRequestHeadersParams, progress?: Progress): Promise<RequestRawRequestHeadersResult>; | ||||||
| } | } | ||||||
|  | export type RequestResponseEvent = {}; | ||||||
| export type RequestResponseParams = {}; | export type RequestResponseParams = {}; | ||||||
| export type RequestResponseOptions = {}; | export type RequestResponseOptions = {}; | ||||||
| export type RequestResponseResult = { | export type RequestResponseResult = { | ||||||
|  | @ -3883,6 +3886,7 @@ export type RequestRawRequestHeadersResult = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export interface RequestEvents { | export interface RequestEvents { | ||||||
|  |   'response': RequestResponseEvent; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ----------- Route -----------
 | // ----------- Route -----------
 | ||||||
|  |  | ||||||
|  | @ -3447,6 +3447,7 @@ Request: | ||||||
|       items: NameValue |       items: NameValue | ||||||
|     isNavigationRequest: boolean |     isNavigationRequest: boolean | ||||||
|     redirectedFrom: Request? |     redirectedFrom: Request? | ||||||
|  |     hasResponse: boolean | ||||||
| 
 | 
 | ||||||
|   commands: |   commands: | ||||||
| 
 | 
 | ||||||
|  | @ -3462,6 +3463,8 @@ Request: | ||||||
|           type: array |           type: array | ||||||
|           items: NameValue |           items: NameValue | ||||||
| 
 | 
 | ||||||
|  |   events: | ||||||
|  |     response: | ||||||
| 
 | 
 | ||||||
| Route: | Route: | ||||||
|   type: interface |   type: interface | ||||||
|  |  | ||||||
|  | @ -290,3 +290,61 @@ Running 1 test using 1 worker | ||||||
| ### Paused on error: | ### Paused on error: | ||||||
| Error: expect(locator).toBeVisible() failed`));
 | Error: expect(locator).toBeVisible() failed`));
 | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | test('test_debug w/ console_messages', async ({ startClient }) => { | ||||||
|  |   const { client, id } = await prepareDebugTest(startClient, ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       test('fail', async ({ page }) => { | ||||||
|  |         await page.evaluate(() => { | ||||||
|  |           console.log('console.log'); | ||||||
|  |           console.error('console.error'); | ||||||
|  |         }); | ||||||
|  |         await expect(page.getByRole('button', { name: 'Missing' })).toBeVisible({ timeout: 1000 }); | ||||||
|  |       }); | ||||||
|  |   `);
 | ||||||
|  | 
 | ||||||
|  |   expect(await client.callTool({ | ||||||
|  |     name: 'test_debug', | ||||||
|  |     arguments: { | ||||||
|  |       test: { id, title: 'fail' }, | ||||||
|  |     }, | ||||||
|  |   })).toHaveTextResponse(expect.stringContaining(` | ||||||
|  | Running 1 test using 1 worker | ||||||
|  | ### Paused on error: | ||||||
|  | Error: expect(locator).toBeVisible() failed`));
 | ||||||
|  | 
 | ||||||
|  |   expect(await client.callTool({ | ||||||
|  |     name: 'browser_console_messages', | ||||||
|  |   })).toHaveResponse({ | ||||||
|  |     result: expect.stringMatching(/\[LOG] console.log.*\n\[ERROR\] console.error/), | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('test_debug w/ network_requests', async ({ startClient, server }) => { | ||||||
|  |   const { client, id } = await prepareDebugTest(startClient, ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       test('fail', async ({ page }) => { | ||||||
|  |         await page.goto(${JSON.stringify(server.HELLO_WORLD)}); | ||||||
|  |         await page.evaluate(async () => { | ||||||
|  |           await fetch('missing'); | ||||||
|  |         }); | ||||||
|  |         await expect(page.getByRole('button', { name: 'Missing' })).toBeVisible({ timeout: 1000 }); | ||||||
|  |       }); | ||||||
|  |   `);
 | ||||||
|  | 
 | ||||||
|  |   expect(await client.callTool({ | ||||||
|  |     name: 'test_debug', | ||||||
|  |     arguments: { | ||||||
|  |       test: { id, title: 'fail' }, | ||||||
|  |     }, | ||||||
|  |   })).toHaveTextResponse(expect.stringContaining(` | ||||||
|  | Running 1 test using 1 worker | ||||||
|  | ### Paused on error: | ||||||
|  | Error: expect(locator).toBeVisible() failed`));
 | ||||||
|  | 
 | ||||||
|  |   expect(await client.callTool({ | ||||||
|  |     name: 'browser_network_requests', | ||||||
|  |   })).toHaveResponse({ | ||||||
|  |     result: `\[GET\] ${server.HELLO_WORLD} => [200] OK\n\[GET\] ${server.PREFIX}/missing => [404] Not Found`, | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue