diff --git a/.github/agents/demonstrate.md b/.github/agents/demonstrate.md new file mode 100644 index 0000000000000..1a59be8b21178 --- /dev/null +++ b/.github/agents/demonstrate.md @@ -0,0 +1,109 @@ +--- +name: Demonstrate +description: Agent for demonstrating VS Code features +target: github-copilot +tools: ['edit', 'search', 'vscode-playwright-mcp/*', 'github/github-mcp-server/*', 'usages', 'fetch', 'githubRepo', 'todos'] +--- + +# Role and Objective + +You are a QA testing agent. Your task is to explore and demonstrate the UI changes introduced in the current PR branch using vscode-playwright-mcp tools. Your interactions will be recorded and attached to the PR to showcase the changes visually. + +# Core Requirements + +## Setup Phase + +1. Use GitHub MCP tools to get PR details (description, linked issues, comments) +2. Search the `microsoft/vscode-docs` repository for relevant documentation about the feature area +3. Examine changed files and commit messages to understand the scope +4. Identify what UI features or behaviors were modified +5. Start VS Code automation using `vscode_automation_start` +6. ALWAYS start by setting the setting `"chat.allowAnonymousAccess":true` using the `vscode_automation_settings_add_user_settings` tool. This will ensure that Chat works without requiring sign-in. + +## Testing Phase + +1. Use `browser_snapshot` to capture the current state +2. Execute the user workflows affected by the PR changes + +## Demonstration Goals + +- Show the new or modified UI in action +- Exercise the changed code paths through realistic user interactions +- Capture clear visual evidence of the improvements or changes +- Test edge cases or variations if applicable + +# Important Guidelines + +- Focus on DEMONSTRATING the changes, not verifying correctness +- You are NOT writing playwright tests - use the tools interactively to explore +- If the PR description or commits mention specific scenarios, prioritize testing those +- Make multiple passes if needed to capture different aspects of the changes +- You may make temporary modifications to facilitate better demonstration (e.g., adjusting settings, opening specific views) + +## GitHub MCP Tools + +**Prefer using GitHub MCP tools over `gh` CLI commands** - these provide structured data and better integration: + +### Pull Request Tools +- `pull_request_read` - Get PR details, diff, status, files, reviews, and comments + - Use `method="get"` for PR metadata (title, description, labels, etc.) + - Use `method="get_diff"` for the full diff + - Use `method="get_files"` for list of changed files + - Use `method="get_reviews"` for review summaries + - Use `method="get_review_comments"` for line-specific review comments +- `search_pull_requests` - Search PRs with filters (author, state, etc.) + +### Issue Tools +- `get_issue` - Get full issue details (description, labels, assignees, etc.) +- `get_issue_comments` - Get all comments on an issue +- `search_issues` - Search issues with filters +- `list_sub_issues` - Get sub-issues if using issue hierarchies + +## Pointers for Controlling VS Code + +- **Prefer `vscode_automation_*` tools over `browser_*` tools** when available - these are designed specifically for VS Code interactions and provide more reliable control. For example: + - `vscode_automation_chat_send_message` over using `browser_*` tools to send chat messages + - `vscode_automation_editor_type_text` over using `browser_*` tools to type in editors + +If you are typing into a monaco input and you can't use the standard methods, follow this sequence: + +**Monaco editors (used throughout VS Code) DO NOT work with standard Playwright methods like `.click()` on textareas or `.fill()` / `.type()`** + +**YOU MUST follow this exact sequence:** + +1. **Take a page snapshot** to identify the editor structure in the accessibility tree +2. **Find the parent `code` role element** that wraps the Monaco editor + - ❌ DO NOT click on `textarea` or `textbox` elements - these are overlaid by Monaco's rendering + - ✅ DO click on the `code` role element that is the parent container +3. **Click on the `code` element** to focus the editor - this properly delegates focus to Monaco's internal text handling +4. **Verify focus** by checking that the nested textbox element has the `[active]` attribute in a new snapshot +5. **Use `page.keyboard.press()` for EACH character individually** - standard Playwright `type()` or `fill()` methods don't work with Monaco editors since they intercept keyboard events at the page level + +**Example:** +```js +// ❌ WRONG - this will fail with timeout +await page.locator('textarea').click(); +await page.locator('textarea').fill('text'); + +// ✅ CORRECT +await page.locator('[role="code"]').click(); +await page.keyboard.press('t'); +await page.keyboard.press('e'); +await page.keyboard.press('x'); +await page.keyboard.press('t'); +``` + +**Why this is required:** Monaco editors intercept keyboard events at the page level and use a virtualized rendering system. Clicking textareas directly or using `.fill()` bypasses Monaco's event handling, causing timeouts and failures. + +# Workflow Pattern + +1. Gather context: + - Retrieve PR details using GitHub MCP (description, linked issues, review comments) + - Search microsoft/vscode-docs for documentation on the affected feature areas + - Examine changed files and commit messages +2. Plan which user interactions will best showcase the changes +3. Start automation and navigate to the relevant area +4. Perform the interactions +5. Document what you're demonstrating as you go +6. Ensure the recording clearly shows the before/after or new functionality +7. **ALWAYS stop the automation** by calling `vscode_automation_stop` - this is REQUIRED whether you successfully demonstrated the feature or encountered issues that prevented testing diff --git a/.github/prompts/playwright.prompt.md b/.github/prompts/playwright.prompt.md deleted file mode 100644 index 107c91475c0da..0000000000000 --- a/.github/prompts/playwright.prompt.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -mode: agent -description: 'Use playwright & automation tools to _see_ the code changes you have made' -tools: ['codebase', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'findTestFiles', 'searchResults', 'githubRepo', 'todos', 'runTests', 'editFiles', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'vscode-playwright-mcp', 'get_commit', 'get_discussion', 'get_discussion_comments', 'get_issue', 'get_issue_comments'] ---- -You are being requested to visually confirm the code changes you are making using vscode-playwright-mcp. - -You MUST run vscode_automation_start & browser_snapshot. -You MUST verify the bad behavior you are investigating using vscode-playwright-mcp. -You MUST verify the code changes you have made using vscode-playwright-mcp. -You MUST take before and after screenshots. -Remember, you are NOT writing playwright tests; instead, focus on using the tools to validate and explore the changes. -You MAY need to make multiple passes, iterating between making code changes and verifying them with the tools. -You MUST reload the window (`Developer: Reload Window` command) after making changes to ensure they are applied correctly. -You MAY make temporary changes to the code to facilitate testing and exploration. For example, using the quick pick in the Configure Display Language action as a scratch pad to add buttons to it. diff --git a/test/automation/src/chat.ts b/test/automation/src/chat.ts new file mode 100644 index 0000000000000..ce308b51a5cdb --- /dev/null +++ b/test/automation/src/chat.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Code } from './code'; +import { Notification } from './notification'; + +const CHAT_VIEW = 'div[id="workbench.panel.chat"]'; +const CHAT_INPUT = `${CHAT_VIEW} .monaco-editor[role="code"]`; +const CHAT_INPUT_FOCUSED = `${CHAT_VIEW} .monaco-editor.focused[role="code"]`; + +export class Chat { + + constructor(private code: Code, private notification: Notification) { } + + async waitForChatView(): Promise { + await this.code.waitForElement(CHAT_VIEW); + } + + async waitForInputFocus(): Promise { + await this.code.waitForElement(CHAT_INPUT_FOCUSED); + } + + async sendMessage(message: string): Promise { + if (await this.notification.isNotificationVisible()) { + throw new Error('Notification is visible'); + } + // Click on the chat input to focus it + await this.code.waitAndClick(CHAT_INPUT); + + // Wait for the editor to be focused + await this.waitForInputFocus(); + + // Dispatch a paste event with the message + await this.code.driver.currentPage.evaluate(({ selector, text }: { selector: string; text: string }) => { + const element = document.querySelector(selector); + if (element) { + const dataTransfer = new DataTransfer(); + dataTransfer.setData('text/plain', text); + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true + }); + element.dispatchEvent(pasteEvent); + } + }, { selector: CHAT_INPUT, text: message }); + + // Submit the message + await this.code.dispatchKeybinding('enter', () => Promise.resolve()); + } +} diff --git a/test/automation/src/index.ts b/test/automation/src/index.ts index b0f1b0f14224b..29fdf0fd54f67 100644 --- a/test/automation/src/index.ts +++ b/test/automation/src/index.ts @@ -26,4 +26,6 @@ export * from './viewlet'; export * from './localization'; export * from './workbench'; export * from './task'; +export * from './chat'; +export * from './notification'; export { getDevElectronPath, getBuildElectronPath, getBuildVersion } from './electron'; diff --git a/test/automation/src/notification.ts b/test/automation/src/notification.ts new file mode 100644 index 0000000000000..1145dc4a29b24 --- /dev/null +++ b/test/automation/src/notification.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Code } from './code'; + +const NOTIFICATION_TOAST = '.notification-toast'; + +export class Notification { + + constructor(private code: Code) { } + + async isNotificationVisible(): Promise { + try { + await this.code.waitForElement(NOTIFICATION_TOAST, undefined, 1); + return true; + } catch { + return false; + } + } +} diff --git a/test/automation/src/workbench.ts b/test/automation/src/workbench.ts index 3490587431016..1c624db35d7f3 100644 --- a/test/automation/src/workbench.ts +++ b/test/automation/src/workbench.ts @@ -22,6 +22,8 @@ import { Terminal } from './terminal'; import { Notebook } from './notebook'; import { Localization } from './localization'; import { Task } from './task'; +import { Chat } from './chat'; +import { Notification } from './notification'; export interface Commands { runCommand(command: string, options?: { exactLabelMatch?: boolean }): Promise; @@ -47,6 +49,8 @@ export class Workbench { readonly notebook: Notebook; readonly localization: Localization; readonly task: Task; + readonly chat: Chat; + readonly notification: Notification; constructor(code: Code) { this.editors = new Editors(code); @@ -67,5 +71,7 @@ export class Workbench { this.notebook = new Notebook(this.quickaccess, this.quickinput, code); this.localization = new Localization(code); this.task = new Task(code, this.editor, this.editors, this.quickaccess, this.quickinput, this.terminal); + this.notification = new Notification(code); + this.chat = new Chat(code, this.notification); } } diff --git a/test/mcp/src/automationTools/chat.ts b/test/mcp/src/automationTools/chat.ts new file mode 100644 index 0000000000000..5157bf19ddd88 --- /dev/null +++ b/test/mcp/src/automationTools/chat.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { ApplicationService } from '../application'; +import { z } from 'zod'; + +/** + * Chat Tools + */ +export function applyChatTools(server: McpServer, appService: ApplicationService): RegisteredTool[] { + const tools: RegisteredTool[] = []; + + tools.push(server.tool( + 'vscode_automation_chat_send_message', + 'Send a message to the VS Code chat panel', + { + message: z.string().describe('The message to send to the chat') + }, + async (args) => { + const { message } = args; + const app = await appService.getOrCreateApplication(); + try { + await app.workbench.chat.sendMessage(message); + return { + content: [{ + type: 'text' as const, + text: `Sent chat message: "${message}"` + }] + }; + } catch (error) { + return { + content: [{ + type: 'text' as const, + text: `Failed to send chat message: ${error}` + }] + }; + } + } + )); + + return tools; +} diff --git a/test/mcp/src/automationTools/index.ts b/test/mcp/src/automationTools/index.ts index 8bcbc3705b81c..12c5d04a72572 100644 --- a/test/mcp/src/automationTools/index.ts +++ b/test/mcp/src/automationTools/index.ts @@ -24,6 +24,7 @@ import { applyNotebookTools } from './notebook.js'; import { applyLocalizationTools } from './localization.js'; import { applyTaskTools } from './task.js'; import { applyProfilerTools } from './profiler.js'; +import { applyChatTools } from './chat.js'; import { ApplicationService } from '../application'; /** @@ -89,6 +90,9 @@ export function applyAllTools(server: McpServer, appService: ApplicationService) // Profiler Tools tools = tools.concat(applyProfilerTools(server, appService)); + // Chat Tools + tools = tools.concat(applyChatTools(server, appService)); + // Return all registered tools return tools; } @@ -112,5 +116,6 @@ export { applyNotebookTools, applyLocalizationTools, applyTaskTools, - applyProfilerTools + applyProfilerTools, + applyChatTools };