feat: allow console / network on pause (#37593)

This commit is contained in:
Pavel Feldman 2025-09-25 18:16:50 -07:00 committed by GitHub
parent 888da0f89d
commit 4112695fab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 111 additions and 26 deletions

View File

@ -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

View File

@ -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
--- ---

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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'])),

View File

@ -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> {

View File

@ -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 {

View File

@ -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

View File

@ -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;
} }

View File

@ -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()));
}, },
}); });

View File

@ -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(' ');
} }

View File

@ -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 -----------

View File

@ -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

View File

@ -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`,
});
});