diff --git a/README.md b/README.md index aab39c1..d69ab09 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A Model Context Protocol (MCP) server that provides browser automation capabilit [Install in VS Code](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D) [Install in VS Code Insiders](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D) @@ -142,7 +142,8 @@ The Playwright MCP server can be configured using a JSON configuration file. Her 'history' | // Browser history 'wait' | // Wait utilities 'files' | // File handling - 'install' // Browser installation + 'install' | // Browser installation + 'testing' // Testing >; // Enable vision mode (screenshots instead of accessibility snapshots) @@ -485,4 +486,15 @@ X Y coordinate space, based on the provided screenshot. - `accept` (boolean): Whether to accept the dialog. - `promptText` (string, optional): The text of the prompt in case of a prompt dialog. +### Testing + + + +- **browser_generate_playwright_test** + - Description: Generate a Playwright test for given scenario + - Parameters: + - `name` (string): The name of the test + - `description` (string): The description of the test + - `steps` (array): The steps of the test + diff --git a/config.d.ts b/config.d.ts index d0c8e19..65ebec3 100644 --- a/config.d.ts +++ b/config.d.ts @@ -16,7 +16,7 @@ import type * as playwright from 'playwright'; -export type ToolCapability = 'core' | 'tabs' | 'pdf' | 'history' | 'wait' | 'files' | 'install'; +export type ToolCapability = 'core' | 'tabs' | 'pdf' | 'history' | 'wait' | 'files' | 'install' | 'testing'; export type Config = { /** diff --git a/examples.md b/examples.md deleted file mode 100644 index 1da45da..0000000 --- a/examples.md +++ /dev/null @@ -1,47 +0,0 @@ -# Examples - -Below are some examples of prompts to use with Playwright MCP. Draw inspiration from them to create your own. If you come up with a new prompt that works well, feel free to let us know in an issue or pull request. - -## Write a regression test for a bug report - -We have received a user report about a bug on our website https://debs-obrien.github.io/playwright-movies-app. I'd like you to verify if this bug exists by trying to follow the steps. If you see different behaviour, explain how it's different. If you see the same behaviour, please write me a Playwright regression test that encapsulates the buggy user flow. While writing the test, draw inspiration from the Playwright code you ran. Here's the user report: - -Howdy. I love your movies site, but there's something fishy going on. I'm looking at The Garfield Movie - a spectacular film - but somehow it's showing the wrong length. The movie is 90mins long, I measured myself yesterday! But somehow your site is claiming 101mins. Am I looking at it wrong, or is there a bug? - -**Output:** - -```ts -import { test, expect } from '@playwright/test'; - -/** - * Regression test for bug: The Garfield Movie showing incorrect runtime - * User reported actual runtime is 90 minutes, but site shows 101 minutes - */ -test('verify The Garfield Movie runtime bug', async ({ page }) => { - // Navigate to the movies app home page - await page.goto('https://debs-obrien.github.io/playwright-movies-app'); - - // Wait for page to load completely - await expect(page.getByRole('heading', { name: 'Popular', level: 1 })).toBeVisible(); - - // Look for The Garfield Movie and click on it - const garfieldMovieLink = page.getByRole('link', { name: /poster of The Garfield Movie/i }); - await expect(garfieldMovieLink).toBeVisible(); - await garfieldMovieLink.click(); - - // Verify we're on the correct movie page - await expect(page.getByRole('heading', { name: 'The Garfield Movie', level: 1 })).toBeVisible(); - - // Check the runtime information - // The bug: Expected runtime should be 90 min, but site shows 101 min - const movieInfoText = await page.locator('text=English / 101 min. / 2024').textContent(); - - // This test will fail because of the bug (which is what we want to demonstrate) - // Once fixed, this assertion should be updated to the correct runtime (90 min) - expect(movieInfoText).toContain('90 min'); - - // Alternative assertion that verifies the incorrect runtime is still present - // Uncomment this and comment the above assertion to verify the bug exists - // expect(movieInfoText).toContain('101 min'); -}); -``` diff --git a/examples/generate-test.md b/examples/generate-test.md new file mode 100644 index 0000000..ef97d2c --- /dev/null +++ b/examples/generate-test.md @@ -0,0 +1,11 @@ +Generate test for scenario: + +## GitHub PR Checks Navigation Checklist + +1. Open the [Microsoft Playwright GitHub repository](https://github.com/microsoft/playwright). +2. Click on the **Pull requests** tab. +3. Find and open the pull request titled **"chore: make noWaitAfter a default"**. +4. Switch to the **Checks** tab for that pull request. +5. Expand the **infra** check suite to view its jobs. +6. Click on the **docs & lint** job to view its details. +7. Confirm that the job log page loads (and optionally, that the log is expired). diff --git a/src/index.ts b/src/index.ts index 1b45d6b..25ff2d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,7 +27,7 @@ import pdf from './tools/pdf.js'; import snapshot from './tools/snapshot.js'; import tabs from './tools/tabs.js'; import screen from './tools/screen.js'; - +import testing from './tools/testing.js'; import type { Tool } from './tools/tool.js'; import type { Config } from '../config.js'; import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; @@ -44,6 +44,7 @@ const snapshotTools: Tool[] = [ ...pdf, ...snapshot, ...tabs(true), + ...testing, ]; const screenshotTools: Tool[] = [ @@ -58,6 +59,7 @@ const screenshotTools: Tool[] = [ ...pdf, ...screen, ...tabs(false), + ...testing, ]; import packageJSON from '../package.json' with { type: 'json' }; diff --git a/src/tools/testing.ts b/src/tools/testing.ts new file mode 100644 index 0000000..f6292cd --- /dev/null +++ b/src/tools/testing.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod'; +import { defineTool } from './tool.js'; + +const generateTestSchema = z.object({ + name: z.string().describe('The name of the test'), + description: z.string().describe('The description of the test'), + steps: z.array(z.string()).describe('The steps of the test'), +}); + +const generateTest = defineTool({ + capability: 'testing', + + schema: { + name: 'browser_generate_playwright_test', + description: 'Generate a Playwright test for given scenario', + inputSchema: generateTestSchema, + }, + + handle: async (context, params) => { + return { + resultOverride: { + content: [{ + type: 'text', + text: instructions(params), + }], + }, + code: [], + captureSnapshot: false, + waitForNetwork: false, + }; + }, +}); + +const instructions = (params: { name: string, description: string, steps: string[] }) => [ + `You are a playwright test generator.`, + `You are given a scenario and you need to generate a playwright test for it.`, + 'Do not generate test code based on the scenario. Run steps one by one using the tools provided instead.', + 'Once all steps are completed, emit a Playwright TypeScript test that uses @playwright/test based on message history', + `Test name: ${params.name}`, + `Description: ${params.description}`, + `Steps:`, + ...params.steps.map((step, index) => `- ${index + 1}. ${step}`), +].join('\n'); + +export default [ + generateTest, +]; diff --git a/tests/capabilities.spec.ts b/tests/capabilities.spec.ts index 5952d09..4187be2 100644 --- a/tests/capabilities.spec.ts +++ b/tests/capabilities.spec.ts @@ -23,6 +23,7 @@ test('test snapshot tool list', async ({ client }) => { 'browser_console_messages', 'browser_drag', 'browser_file_upload', + 'browser_generate_playwright_test', 'browser_handle_dialog', 'browser_hover', 'browser_select_option', @@ -52,6 +53,7 @@ test('test vision tool list', async ({ visionClient }) => { 'browser_close', 'browser_console_messages', 'browser_file_upload', + 'browser_generate_playwright_test', 'browser_handle_dialog', 'browser_install', 'browser_navigate_back', diff --git a/utils/generate_links.js b/utils/generate-links.js similarity index 100% rename from utils/generate_links.js rename to utils/generate-links.js diff --git a/utils/update-readme.js b/utils/update-readme.js index 93513df..35d5e63 100644 --- a/utils/update-readme.js +++ b/utils/update-readme.js @@ -32,6 +32,7 @@ import pdfTools from '../lib/tools/pdf.js'; import snapshotTools from '../lib/tools/snapshot.js'; import tabsTools from '../lib/tools/tabs.js'; import screenTools from '../lib/tools/screen.js'; +import testTools from '../lib/tools/testing.js'; // Category definitions for tools const categories = { @@ -62,6 +63,9 @@ const categories = { ...installTools, ...dialogsTools(true), ], + 'Testing': [ + ...testTools, + ], }; // NOTE: Can be removed when we drop Node.js 18 support and changed to import.meta.filename.