Skip to content

Commit 8e0215a

Browse files
committed
patch
1 parent bc48600 commit 8e0215a

File tree

9 files changed

+109
-12
lines changed

9 files changed

+109
-12
lines changed

package-lock.json

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
{
2-
"name": "@playwright/mcp",
2+
"name": "@aethr/playwright-mcp",
33
"version": "0.0.13",
44
"description": "Playwright Tools for MCP",
55
"repository": {
66
"type": "git",
7-
"url": "git+https://github.com/microsoft/playwright-mcp.git"
7+
"url": "git+https://github.com/autifyhq/aethr-playwright-mcp.git"
88
},
9-
"homepage": "https://playwright.dev",
109
"engines": {
1110
"node": ">=18"
1211
},
1312
"author": {
14-
"name": "Microsoft Corporation"
13+
"name": "Autify Inc."
1514
},
1615
"license": "Apache-2.0",
1716
"scripts": {
@@ -32,6 +31,7 @@
3231
},
3332
"dependencies": {
3433
"@modelcontextprotocol/sdk": "^1.6.1",
34+
"@playwright/test": "^1.52.0-alpha-1743163434000",
3535
"commander": "^13.1.0",
3636
"playwright": "^1.52.0-alpha-1743163434000",
3737
"yaml": "^2.7.1",
@@ -40,7 +40,6 @@
4040
"devDependencies": {
4141
"@eslint/eslintrc": "^3.2.0",
4242
"@eslint/js": "^9.19.0",
43-
"@playwright/test": "^1.52.0-alpha-1743163434000",
4443
"@stylistic/eslint-plugin": "^3.0.1",
4544
"@types/node": "^22.13.10",
4645
"@typescript-eslint/eslint-plugin": "^8.26.1",

src/context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ export class Context {
122122
async close() {
123123
if (!this._browserContext)
124124
return;
125+
if (process.env.TRACE)
126+
await this._browserContext.tracing.stop({ path: process.env.TRACE });
125127
await this._browserContext.close();
126128
}
127129

@@ -133,6 +135,8 @@ export class Context {
133135
for (const page of this._browserContext.pages())
134136
this._onPageCreated(page);
135137
this._browserContext.on('page', page => this._onPageCreated(page));
138+
if (process.env.TRACE)
139+
await this._browserContext.tracing.start({ screenshots: true, snapshots: true, sources: true });
136140
}
137141
return this._browserContext;
138142
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type { Tool, ToolCapability } from './tools/tool';
3030
import type { Resource } from './resources/resource';
3131
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
3232
import type { LaunchOptions } from 'playwright';
33+
import assert from './tools/assert';
3334

3435
const snapshotTools: Tool[] = [
3536
...common(true),
@@ -40,6 +41,7 @@ const snapshotTools: Tool[] = [
4041
...pdf,
4142
...snapshot,
4243
...tabs(true),
44+
...assert,
4345
];
4446

4547
const screenshotTools: Tool[] = [

src/tools/assert.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Copyright (c) Autify Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { z } from 'zod';
18+
import { Tool } from './tool';
19+
import zodToJsonSchema from 'zod-to-json-schema';
20+
import { expect } from '@playwright/test';
21+
import { replaceEnvVar } from './utils';
22+
23+
const elementSchema = z.object({
24+
element: z.string().describe('Human-readable element description used to obtain permission to interact with the element'),
25+
ref: z.string().describe('Exact target element reference from the page snapshot'),
26+
});
27+
28+
const assertContainTextSchema = elementSchema.partial().extend({
29+
against: z.enum(['element', 'page']).describe('Assert against the specified element or the whole page. If page, element and ref are not needed.'),
30+
expected: z.string().describe('Expected text to be contained in the specified element or the whole page'),
31+
});
32+
33+
const assertContainText: Tool = {
34+
capability: 'core',
35+
schema: {
36+
name: 'browser_assert_contain_text',
37+
description: 'Assert that the element or the whole page contains the expected text. It returns JSON having "result" (PASS or FAIL), "against" (assert against element or page) and "error" (details if result is FAIL).',
38+
inputSchema: zodToJsonSchema(assertContainTextSchema),
39+
},
40+
41+
handle: async (context, params) => {
42+
const validatedParams = assertContainTextSchema.parse(params);
43+
try {
44+
if (validatedParams.against === 'element') {
45+
if (validatedParams.ref === undefined)
46+
throw new Error('ref is required when asserting against an element');
47+
const locator = context.currentTab().lastSnapshot().refLocator(validatedParams.ref);
48+
await expect(locator).toContainText(replaceEnvVar(validatedParams.expected));
49+
return {
50+
content: [{
51+
type: 'text',
52+
text: JSON.stringify({ result: 'PASS', against: 'element' }),
53+
}],
54+
};
55+
} else {
56+
const locator = context.currentTab().page.locator('body');
57+
await expect(locator).toContainText(replaceEnvVar(validatedParams.expected));
58+
return {
59+
content: [{
60+
type: 'text',
61+
text: JSON.stringify({ result: 'PASS', against: 'page' }),
62+
}],
63+
};
64+
}
65+
} catch (err) {
66+
const error = err instanceof Error ? err.message : String(err);
67+
return {
68+
content: [{
69+
type: 'text',
70+
text: JSON.stringify({ result: 'FAIL', error, against: validatedParams.against }),
71+
}],
72+
};
73+
}
74+
},
75+
};
76+
77+
export default [assertContainText];

src/tools/navigate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { z } from 'zod';
1818
import { zodToJsonSchema } from 'zod-to-json-schema';
1919

2020
import type { ToolFactory } from './tool';
21+
import { replaceEnvVar } from './utils';
2122

2223
const navigateSchema = z.object({
2324
url: z.string().describe('The URL to navigate to'),
@@ -34,7 +35,7 @@ const navigate: ToolFactory = captureSnapshot => ({
3435
const validatedParams = navigateSchema.parse(params);
3536
const currentTab = await context.ensureTab();
3637
return await currentTab.run(async tab => {
37-
await tab.navigate(validatedParams.url);
38+
await tab.navigate(replaceEnvVar(validatedParams.url));
3839
const code = [
3940
`// Navigate to ${validatedParams.url}`,
4041
`await page.goto('${validatedParams.url}');`,

src/tools/snapshot.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type * as playwright from 'playwright';
2121
import type { Tool } from './tool';
2222
import { generateLocator } from '../context';
2323
import * as javascript from '../javascript';
24+
import { replaceEnvVar } from './utils';
2425

2526
const snapshot: Tool = {
2627
capability: 'core',
@@ -141,11 +142,11 @@ const type: Tool = {
141142
if (validatedParams.slowly) {
142143
code.push(`// Press "${validatedParams.text}" sequentially into "${validatedParams.element}"`);
143144
code.push(`await page.${await generateLocator(locator)}.pressSequentially(${javascript.quote(validatedParams.text)});`);
144-
await locator.pressSequentially(validatedParams.text);
145+
await locator.pressSequentially(replaceEnvVar(validatedParams.text));
145146
} else {
146147
code.push(`// Fill "${validatedParams.text}" into "${validatedParams.element}"`);
147148
code.push(`await page.${await generateLocator(locator)}.fill(${javascript.quote(validatedParams.text)});`);
148-
await locator.fill(validatedParams.text);
149+
await locator.fill(replaceEnvVar(validatedParams.text));
149150
}
150151
if (validatedParams.submit) {
151152
code.push(`// Submit text`);

src/tools/utils.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,16 @@ export async function waitForCompletion<R>(page: playwright.Page, callback: () =
7272
export function sanitizeForFilePath(s: string) {
7373
return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
7474
}
75+
76+
export function replaceEnvVar(input: string): string {
77+
if (input.startsWith('${') && input.endsWith('}')) {
78+
const variable = input.slice(2, -1);
79+
const envValue = process.env[variable];
80+
if (envValue)
81+
return envValue;
82+
else
83+
return input;
84+
} else {
85+
return input;
86+
}
87+
}

tests/capabilities.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { test, expect } from './fixtures';
1919
test('test snapshot tool list', async ({ client }) => {
2020
const { tools } = await client.listTools();
2121
expect(new Set(tools.map(t => t.name))).toEqual(new Set([
22+
'browser_assert_contain_text',
2223
'browser_click',
2324
'browser_drag',
2425
'browser_file_upload',

0 commit comments

Comments
 (0)