Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/playwright-core/src/server/agent/actionRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ async function innerRunAction(progress: Progress, mode: 'generate' | 'run', page
await runExpect(frame, progress, mode, undefined, { expression: 'to.have.url', expectedText, isNot: !!action.isNot }, expected, 'toHaveURL', 'expected');
break;
}
case 'expectTitle': {
const expectedText = serializeExpectedTextValues([action.value], { normalizeWhiteSpace: true });
await runExpect(frame, progress, mode, undefined, { expression: 'to.have.title', expectedText, isNot: !!action.isNot }, action.value, 'toHaveTitle', 'expected');
break;
}
}
}

Expand Down Expand Up @@ -309,6 +314,17 @@ export function traceParamsForAction(progress: Progress, action: actions.Action,
};
return { type: 'Frame', method: 'expect', title: 'Expect URL', params };
}
case 'expectTitle': {
const expectedText = serializeExpectedTextValues([action.value], { normalizeWhiteSpace: true });
const params: channels.FrameExpectParams = {
selector: undefined,
expression: 'to.have.title',
expectedText,
isNot: !!action.isNot,
timeout: expectTimeout(mode),
};
return { type: 'Frame', method: 'expect', title: 'Expect Title', params };
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions packages/playwright-core/src/server/agent/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ const expectURLSchema = zod.object({
});
export type ExpectURL = z.infer<typeof expectURLSchema>;

const expectTitleSchema = zod.object({
method: zod.literal('expectTitle'),
value: zod.string(),
isNot: zod.boolean().optional(),
});
export type ExpectTitle = z.infer<typeof expectTitleSchema>;

const actionSchema = zod.discriminatedUnion('method', [
navigateActionSchema,
clickActionSchema,
Expand All @@ -131,6 +138,7 @@ const actionSchema = zod.discriminatedUnion('method', [
expectValueSchema,
expectAriaSchema,
expectURLSchema,
expectTitleSchema,
]);
export type Action = z.infer<typeof actionSchema>;

Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/server/agent/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export async function generateCode(sdkLanguage: Language, action: actions.Action
const notInfix = action.isNot ? 'not.' : '';
return `await expect(page).${notInfix}toHaveURL(${arg});`;
}
case 'expectTitle': {
const notInfix = action.isNot ? 'not.' : '';
return `await expect(page).${notInfix}toHaveTitle(${escapeWithQuotes(action.value)});`;
}
}
// @ts-expect-error
throw new Error('Unknown action ' + action.method);
Expand Down
5 changes: 3 additions & 2 deletions packages/playwright-core/src/server/agent/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { BrowserContext } from '../browserContext';
import { runAction } from './actionRunner';
import { generateCode } from './codegen';
import { stripAnsiEscapes } from '../../utils/isomorphic/stringUtils';

import type { Request } from '../network';
import type * as loopTypes from '@lowire/loop';
Expand Down Expand Up @@ -137,7 +138,7 @@ export class Context {

const text: string[] = [];
if (error)
text.push(`# Error\n${error.message}`);
text.push(`# Error\n${stripAnsiEscapes(error.message)}`);
else
text.push(`# Success`);

Expand All @@ -150,7 +151,7 @@ export class Context {
},
'dev.lowire/history': error ? [{
category: 'error',
content: error.message,
content: stripAnsiEscapes(error.message),
}] : [],
},
isError: !!error,
Expand Down
21 changes: 21 additions & 0 deletions packages/playwright-core/src/server/agent/expectTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,31 @@ const expectURL = defineTool({
},
});

const expectTitle = defineTool({
schema: {
name: 'browser_expect_title',
title: 'Expect title',
description: 'Expect the page title to match the expected value.',
inputSchema: z.object({
title: z.string().describe('Expected page title.'),
isNot: z.boolean().optional().describe('Expect the opposite'),
}),
},

handle: async (progress, context, params) => {
return await context.runActionAndWait(progress, {
method: 'expectTitle',
value: params.title,
isNot: params.isNot,
});
},
});

export default [
expectVisible,
expectVisibleText,
expectValue,
expectList,
expectURL,
expectTitle,
] as ToolDefinition<any>[];
3 changes: 2 additions & 1 deletion packages/playwright-core/src/server/agent/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { z } from '../../mcpBundle';
import { stripAnsiEscapes } from '../../utils/isomorphic/stringUtils';
import type zod from 'zod';
import type * as loopTypes from '@lowire/loop';
import type { Context } from './context';
Expand Down Expand Up @@ -110,7 +111,7 @@ export function toolsForLoop(progress: Progress, context: Context, toolDefinitio
return await tool.handle(progress, context, params.arguments);
} catch (error) {
return {
content: [{ type: 'text', text: error.message }],
content: [{ type: 'text', text: stripAnsiEscapes(error.message) }],
isError: true,
};
}
Expand Down
5 changes: 5 additions & 0 deletions packages/playwright-core/src/utils/isomorphic/stringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,8 @@ export function parseRegex(regex: string): RegExp {
const flags = regex.slice(lastSlash + 1);
return new RegExp(source, flags);
}

export const ansiRegex = new RegExp('([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', 'g');
export function stripAnsiEscapes(str: string): string {
return str.replace(ansiRegex, '');
}
5 changes: 1 addition & 4 deletions packages/playwright/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,4 @@ export async function removeDirAndLogToConsole(dir: string) {
}
}

export const ansiRegex = new RegExp('([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', 'g');
export function stripAnsiEscapes(str: string): string {
return str.replace(ansiRegex, '');
}
export { ansiRegex, stripAnsiEscapes } from 'playwright-core/lib/utils';
2 changes: 1 addition & 1 deletion tests/components/ct-react17/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject",
"typecheck": "tsc --noEmit"
"typecheck": "echo \"typecheck disabled because of zod v4 being incompatible with ts 4\""
},
"eslintConfig": {
"extends": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
{
"428b1de09afa2d1be4a61fdf7eaa2277bc1426ed": {
"94919d5202a4e9298507668bb88822a939783915": {
"result": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "I need to verify that an input has the value \"hello\", but according to the page snapshot, the textbox with ref=e2 currently has the value \"bye\". This means the expectation is that it should NOT match, or this is testing a failure case.\n\nLet me call the browser_expect_value tool to assert the expected condition:"
"text": "I need to verify that an input has the value \"hello\", but according to the page snapshot, the textbox currently has the value \"bye\". I'll use the browser_expect_value tool to assert the expected value."
},
{
"type": "tool_call",
"name": "browser_expect_value",
"arguments": {
"type": "textbox",
"element": "input",
"element": "input textbox",
"ref": "e2",
"value": "hello",
"_is_done": true
},
"id": "toolu_01B834GHxjywVnc9UGFxpyGc"
"id": "toolu_017soKmdATMEEVS4yxjbaLs4"
}
],
"stopReason": {
"code": "ok"
}
},
"usage": {
"input": 2044,
"output": 201
"input": 2204,
"output": 176
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"46dd9ff2a304413e15a4579295016e8d2626b5dd": {
"44bfbaa6fe0cb038adf37547ecb000d4f39e243f": {
"result": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "I need to verify that the submit button is visible on the page. Based on the page snapshot, I can see there is a button with the text \"Submit\" and reference e2."
"text": "I'll verify that the submit button is visible on the page."
},
{
"type": "tool_call",
Expand All @@ -15,16 +15,16 @@
"accessibleName": "Submit",
"_is_done": true
},
"id": "toolu_011XpmmmcGH5bLRG1hznFRw2"
"id": "toolu_01Luk6EzYXfyshpYHsWqCzfF"
}
],
"stopReason": {
"code": "ok"
}
},
"usage": {
"input": 2043,
"output": 132
"input": 2203,
"output": 107
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"893eb6be29a43887385345cedf150b6ff229a6e8": {
"result": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll verify that the page title is \"My Page Title\" using the browser_expect_title tool."
},
{
"type": "tool_call",
"name": "browser_expect_title",
"arguments": {
"title": "My Page Title",
"_is_done": true
},
"id": "toolu_01QSwESxdJwhfaSFhvahxFqB"
}
],
"stopReason": {
"code": "ok"
}
},
"usage": {
"input": 2195,
"output": 99
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"d969f4ed24b89f142366f64ad47d7b163b68e298": {
"result": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "I need to verify that the page title is \"Other Title\". I'll use the browser_expect_title tool to assert this condition."
},
{
"type": "tool_call",
"name": "browser_expect_title",
"arguments": {
"title": "Other Title",
"_is_done": true
},
"id": "toolu_01BnWsHUg4s787xMJYmLUNDn"
}
],
"stopReason": {
"code": "ok"
}
},
"usage": {
"input": 2194,
"output": 104
}
}
}
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
{
"01e4dfe3e3fd3764d430e1d983db863b06e4ea31": {
"74935765e910e2883dc5925fd5f947bb528f174d": {
"result": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll verify that the page URL is /page.html."
"text": "I see the issue - the URL contains a server prefix. Let me use a regex pattern to match the URL ending with /counter.html."
},
{
"type": "tool_call",
"name": "browser_expect_url",
"arguments": {
"url": "/page.html",
"regex": "/counter\\.html$/",
"_is_done": true
},
"id": "toolu_01FUsQbLdCVmZyUCS1DwGRrC"
"id": "toolu_018XaEFPSm6aLzqA1BBqRNhu"
}
],
"stopReason": {
"code": "ok"
}
},
"usage": {
"input": 2067,
"output": 91
"input": 2447,
"output": 108
}
},
"d3eb8b6dc11f060023ba45368c64d302b7ff7891": {
"ee1b825529efd3c64fff821e9ce6f2e06433f6d3": {
"result": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "The assertion failed because the actual URL is the full URL \"http://localhost:8939/page.html\" while I was checking for just \"/page.html\". Let me verify using a regex pattern to match the URL ending with /page.html:"
"text": "I'll verify that the page URL is /counter.html using the browser_expect_url tool."
},
{
"type": "tool_call",
"name": "browser_expect_url",
"arguments": {
"regex": "/\\/page\\.html$/",
"url": "/counter.html",
"_is_done": true
},
"id": "toolu_01FDFEjR382yKedBJ8LZgwgZ"
"id": "toolu_01PRTpTFD78UKubB5EzwTAii"
}
],
"stopReason": {
"code": "ok"
}
},
"usage": {
"input": 2285,
"output": 132
"input": 2243,
"output": 99
}
}
}
Loading
Loading