/**
* VSCode Automation MCP Server - Testing Tools
*
* Tools for testing and verification: editor content, UI state assertions.
*
* @author Sukarth Acharya
* @license MIT
*/
import { z } from 'zod';
import { getVSCodeDriver } from '../vscode-driver.js';
import type { SelectorType } from '../types.js';
/**
* Selector type enum for Zod validation
*/
const selectorTypeSchema = z.enum(['css', 'xpath', 'accessibility', 'text']).default('css');
/**
* Input schema for vscode_get_editor_content tool
*/
export const getEditorContentInputSchema = {
// No required inputs - gets the currently active editor
};
/**
* Input schema for vscode_verify_element tool
*/
export const verifyElementInputSchema = {
selector: z.string().describe('The selector to find the element'),
selectorType: selectorTypeSchema.describe('Type of selector'),
shouldExist: z.boolean().optional().default(true).describe('Whether the element should exist'),
shouldBeVisible: z.boolean().optional().describe('Whether the element should be visible'),
shouldBeEnabled: z.boolean().optional().describe('Whether the element should be enabled'),
containsText: z.string().optional().describe('Text the element should contain'),
timeout: z.number().optional().default(5000).describe('Timeout in milliseconds'),
};
/**
* Input schema for vscode_assert_text tool
*/
export const assertTextInputSchema = {
expectedText: z.string().describe('The expected text content'),
selector: z.string().optional().describe('Optional selector to check specific element'),
selectorType: selectorTypeSchema.optional().describe('Type of selector if selector is provided'),
exactMatch: z.boolean().optional().default(false).describe('Whether to require exact match or contains'),
};
/**
* Input schema for vscode_check_file_open tool
*/
export const checkFileOpenInputSchema = {
fileName: z.string().describe('The file name to check for (can be partial name)'),
};
/**
* Get the content of the current editor
*
* This tool retrieves the full text content of the currently active
* text editor in VSCode, along with metadata about the editor.
*
* @example
* // Get content of the active editor
* await getEditorContent({});
*/
export async function getEditorContent(_input: Record<string, never>): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
const driver = getVSCodeDriver();
const result = await driver.getEditorContent();
if (result.success && result.data) {
const { content, info } = result.data;
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
editor: {
fileName: info.fileName,
filePath: info.filePath || null,
isDirty: info.isDirty,
lineCount: info.lineCount,
languageId: info.languageId || null,
},
content: content,
contentLength: content.length,
}, null, 2),
}],
};
}
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: result.error,
}, null, 2),
}],
};
}
/**
* Verify the presence and state of a UI element
*
* This tool checks if a UI element exists and optionally verifies
* its visibility, enabled state, and text content.
*
* @example
* // Check if an element exists
* await verifyElement({ selector: '.explorer-viewlet', selectorType: 'css' });
*
* @example
* // Check if an element is visible
* await verifyElement({ selector: '.sidebar', selectorType: 'css', shouldBeVisible: true });
*
* @example
* // Check if an element contains specific text
* await verifyElement({
* selector: '.statusbar',
* selectorType: 'css',
* containsText: 'TypeScript'
* });
*/
export async function verifyElement(input: {
selector: string;
selectorType?: SelectorType;
shouldExist?: boolean;
shouldBeVisible?: boolean;
shouldBeEnabled?: boolean;
containsText?: string;
timeout?: number;
}): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
const driver = getVSCodeDriver();
const result = await driver.getElement({
value: input.selector,
type: input.selectorType || 'css',
timeout: input.timeout,
});
const elementExists = result.success && result.data !== undefined;
const shouldExist = input.shouldExist !== false; // Default to true
const assertions: Array<{ check: string; expected: unknown; actual: unknown; passed: boolean }> = [];
// Check existence
assertions.push({
check: 'exists',
expected: shouldExist,
actual: elementExists,
passed: shouldExist === elementExists,
});
// Only check other properties if element exists and was expected to exist
if (elementExists && result.data) {
if (input.shouldBeVisible !== undefined) {
assertions.push({
check: 'isVisible',
expected: input.shouldBeVisible,
actual: result.data.isDisplayed,
passed: input.shouldBeVisible === result.data.isDisplayed,
});
}
if (input.shouldBeEnabled !== undefined) {
assertions.push({
check: 'isEnabled',
expected: input.shouldBeEnabled,
actual: result.data.isEnabled,
passed: input.shouldBeEnabled === result.data.isEnabled,
});
}
if (input.containsText !== undefined) {
const containsText = result.data.text.includes(input.containsText);
assertions.push({
check: 'containsText',
expected: input.containsText,
actual: result.data.text,
passed: containsText,
});
}
}
const allPassed = assertions.every(a => a.passed);
const failedAssertions = assertions.filter(a => !a.passed);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: allPassed,
selector: input.selector,
selectorType: input.selectorType || 'css',
allAssertionsPassed: allPassed,
assertions: assertions,
failedAssertions: failedAssertions.length > 0 ? failedAssertions : null,
elementInfo: elementExists && result.data ? {
tagName: result.data.tagName,
text: result.data.text.substring(0, 100) + (result.data.text.length > 100 ? '...' : ''),
isDisplayed: result.data.isDisplayed,
isEnabled: result.data.isEnabled,
} : null,
}, null, 2),
}],
};
}
/**
* Assert that specific text appears in the editor or UI element
*
* This tool checks if specific text content exists in either the
* active editor or a specified UI element.
*
* @example
* // Check if editor contains specific text
* await assertText({ expectedText: 'function main()' });
*
* @example
* // Check for exact match
* await assertText({ expectedText: 'Hello, World!', exactMatch: true });
*
* @example
* // Check text in a specific element
* await assertText({
* expectedText: 'No problems',
* selector: '.problems-count',
* selectorType: 'css'
* });
*/
export async function assertText(input: {
expectedText: string;
selector?: string;
selectorType?: SelectorType;
exactMatch?: boolean;
}): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
const driver = getVSCodeDriver();
let actualText: string;
let source: string;
if (input.selector) {
// Get text from a specific element
const result = await driver.getElement({
value: input.selector,
type: input.selectorType || 'css',
});
if (!result.success || !result.data) {
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: `Element not found: ${input.selector}`,
assertion: 'failed',
}, null, 2),
}],
};
}
actualText = result.data.text;
source = `element(${input.selector})`;
} else {
// Get text from the active editor
const result = await driver.getEditorContent();
if (!result.success || !result.data) {
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: result.error || 'Could not get editor content',
assertion: 'failed',
}, null, 2),
}],
};
}
actualText = result.data.content;
source = `editor(${result.data.info.fileName})`;
}
const passed = input.exactMatch
? actualText === input.expectedText
: actualText.includes(input.expectedText);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: passed,
assertion: passed ? 'passed' : 'failed',
source: source,
expectedText: input.expectedText,
matchType: input.exactMatch ? 'exact' : 'contains',
actualTextLength: actualText.length,
// Only include actual text snippet if assertion failed
actualTextSnippet: !passed
? actualText.substring(0, 200) + (actualText.length > 200 ? '...' : '')
: undefined,
}, null, 2),
}],
};
}
/**
* Check if a specific file is open in the editor
*
* This tool checks whether a file with the specified name is currently
* open in one of VSCode's editor tabs.
*
* @example
* // Check if package.json is open
* await checkFileOpen({ fileName: 'package.json' });
*
* @example
* // Check if any TypeScript file is open
* await checkFileOpen({ fileName: '.ts' });
*/
export async function checkFileOpen(input: {
fileName: string;
}): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
const driver = getVSCodeDriver();
try {
const result = await driver.getEditorContent();
if (result.success && result.data) {
const currentFileName = result.data.info.fileName;
const isMatch = currentFileName.toLowerCase().includes(input.fileName.toLowerCase());
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
fileOpen: isMatch,
searchedFor: input.fileName,
currentEditorFile: currentFileName,
message: isMatch
? `File matching '${input.fileName}' is open: ${currentFileName}`
: `Active editor is '${currentFileName}', does not match '${input.fileName}'`,
}, null, 2),
}],
};
}
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
fileOpen: false,
searchedFor: input.fileName,
message: 'No file is currently open in the editor',
}, null, 2),
}],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: errorMessage,
}, null, 2),
}],
};
}
}
/**
* Wait for a condition to be true
*
* This is a utility function that waits for a condition to become true,
* useful for testing asynchronous UI updates.
*/
export async function waitForCondition(input: {
selector: string;
selectorType?: SelectorType;
condition: 'exists' | 'visible' | 'enabled' | 'hidden' | 'disabled';
timeout?: number;
}): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
const driver = getVSCodeDriver();
const webDriver = await driver.getDriver();
const startTime = Date.now();
const timeout = input.timeout || 10000;
const checkCondition = async (): Promise<boolean> => {
const result = await driver.getElement({
value: input.selector,
type: input.selectorType || 'css',
timeout: 1000,
});
switch (input.condition) {
case 'exists':
return result.success && result.data !== undefined;
case 'visible':
return result.success && result.data?.isDisplayed === true;
case 'enabled':
return result.success && result.data?.isEnabled === true;
case 'hidden':
return !result.success || result.data?.isDisplayed === false;
case 'disabled':
return !result.success || result.data?.isEnabled === false;
default:
return false;
}
};
while (Date.now() - startTime < timeout) {
if (await checkCondition()) {
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
selector: input.selector,
condition: input.condition,
waitTime: Date.now() - startTime,
message: `Condition '${input.condition}' met for selector '${input.selector}'`,
}, null, 2),
}],
};
}
await webDriver.sleep(100);
}
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
selector: input.selector,
condition: input.condition,
timeout: timeout,
error: `Timeout waiting for condition '${input.condition}' on selector '${input.selector}'`,
}, null, 2),
}],
};
}