-
-
Notifications
You must be signed in to change notification settings - Fork 159
feat(mcp): introducing Serenity/JS MCP server #2912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
❌ 4 blocking issues (4 total)
|
Core tests migrated from ts-node to tsx to enable testing the new functionality
Diff Coverage: The code coverage on the diff in this pull request is 52.9%. Total Coverage: This PR will decrease coverage by 2.02%. File Coverage Changes
🛟 Help
|
…in list_capabilities
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
Related tickets: re #2985
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/inspection/TestAutomationPageElementResolverController.ts
Show resolved
Hide resolved
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts
Show resolved
Hide resolved
private testRunner(packages: PackageMetadata[]): TestRunner { | ||
const detectedPackages = new Set(packages.map(metadata => metadata.name)); | ||
|
||
const usesWebdriverIO = (detectedPackages.has('webdriverio') || detectedPackages.has('@wdio/cli')); | ||
const usesProtractor = !! detectedPackages.has('protractor'); | ||
const usesCucumber = !! (detectedPackages.has('cucumber') || detectedPackages.has('@cucumber/cucumber')); | ||
const usesJasmine = !! detectedPackages.has('jasmine'); | ||
const usesMocha = !! detectedPackages.has('mocha'); | ||
const usesPlaywrightTest = !! detectedPackages.has('@playwright/test'); | ||
|
||
switch(true) { | ||
case usesWebdriverIO && usesCucumber: | ||
return 'webdriverio-cucumber'; | ||
case usesWebdriverIO && usesJasmine: | ||
return 'webdriverio-jasmine'; | ||
case usesWebdriverIO && usesMocha: | ||
return 'webdriverio-mocha'; | ||
case usesProtractor && usesCucumber: | ||
return 'protractor-cucumber'; | ||
case usesProtractor && usesJasmine: | ||
return 'protractor-jasmine'; | ||
case usesProtractor && usesMocha: | ||
return 'protractor-mocha'; | ||
case usesPlaywrightTest: | ||
return 'playwright-test'; | ||
case usesCucumber: | ||
return 'cucumber'; | ||
case usesJasmine: | ||
return 'jasmine'; | ||
case usesMocha: | ||
return 'mocha'; | ||
default: | ||
throw new ConfigurationError([ | ||
`Could not determine the test runner used in this project.`, | ||
`Supported test runners are Cucumber, Jasmine, Mocha, Playwright Test, Protractor and WebdriverIO.`, | ||
`Please install one of these test runners and try again.`, | ||
].join(' ')); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 'default' | ||
| 'role' | ||
| 'text' | ||
| 'label' | ||
| 'placeholder' | ||
| 'alt' | ||
| 'title' | ||
| 'test-id' | ||
| 'nth' | ||
| 'first' | ||
| 'last' | ||
| 'visible' | ||
| 'has-text' | ||
| 'has-not-text' | ||
| 'has' | ||
| 'hasNot' | ||
| 'frame' | ||
| 'frame-locator' | ||
| 'and' | ||
| 'or' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private pageElementFromLocator(locator: SerializedLocator, description: string): { question: string; imports: ImportManifest } { | ||
const imports = new Imports({ | ||
'@serenity-js/web': [ 'By' ], | ||
}); | ||
|
||
const { kind, body, options } = locator; | ||
|
||
switch (kind) { | ||
case 'default': { | ||
if (options.hasText !== undefined) { | ||
return { | ||
imports: imports.merge({ | ||
'@serenity-js/assertions': [ 'includes' ], | ||
'@serenity-js/web': [ 'PageElements', 'Text' ], | ||
}).toJSON(), | ||
question: `PageElements.located(By.deepCss(${ this.quote(body as string) })).where(Text, includes(${ this.asText(options.hasText) })).first().describedAs(${ this.quote(description ?? body as string) })`, | ||
} | ||
} | ||
|
||
if (options.hasNotText !== undefined) { | ||
return { | ||
imports: imports.merge({ | ||
'@serenity-js/assertions': [ 'includes', 'not' ], | ||
'@serenity-js/web': [ 'PageElements', 'Text' ], | ||
}).toJSON(), | ||
question: `PageElements.located(By.deepCss(${ this.quote(body as string) })).where(Text, not(includes(${ this.asText(options.hasText) }))).first().describedAs(${ this.quote(description ?? body as string) })`, | ||
} | ||
} | ||
|
||
return { | ||
imports: imports.merge({ | ||
'@serenity-js/web': [ 'PageElement' ], | ||
}).toJSON(), | ||
question: `PageElement.located(By.deepCss(${ this.quote(body as string) })).describedAs(${ this.quote(description ?? body as string) })`, | ||
} | ||
} | ||
|
||
case 'role': { | ||
const attrs: string[] = []; | ||
if (isRegExp(options.name)) { | ||
attrs.push(`name: ${ this.regexToSourceString(options.name) }`); | ||
} | ||
else if (typeof options.name === 'string') { | ||
attrs.push(`name: ${ this.quote(options.name) }`); | ||
|
||
if (options.exact) { | ||
attrs.push(`exact: true`); | ||
} | ||
} | ||
for (const { name, value } of options.attrs!) { | ||
attrs.push(`${ name }: ${ typeof value === 'string' ? this.quote(value) : value }`); | ||
} | ||
|
||
const attributesString = attrs.length > 0 ? `, { ${ attrs.join(', ') } }` : ''; | ||
return { | ||
imports: imports.merge({ | ||
'@serenity-js/web': [ 'PageElement' ], | ||
}).toJSON(), | ||
question: `PageElement.located(By.role(${ this.quote(body as string) }${ attributesString })).describedAs(${ this.quote(description ?? body as string) })`, | ||
}; | ||
} | ||
} | ||
|
||
throw new Error(`Locator kind "${ kind }" is not yet supported`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private async scanProjectRuntime(rootDirectory: string): Promise<RuntimeScanResult> { | ||
const originalWorkingDirectory = process.cwd(); | ||
|
||
// todo: try/catch if the rootDirectory not present or invalid | ||
process.chdir(rootDirectory); | ||
|
||
const scanResult = JSON.parse(await envinfo.run({ | ||
Binaries: [ 'Node', 'npm', 'pnpm', 'yarn' ], | ||
Languages: [ 'Java' ], | ||
System: [ 'Container', 'CPU', 'Memory', 'OS', 'Shell' ], | ||
Utilities: [ 'Git' ], | ||
}, { json: true, showNotFound: true, console: false, fullTree: true })); | ||
|
||
process.chdir(originalWorkingDirectory); | ||
|
||
const commands = []; | ||
const instructions = []; | ||
|
||
if (scanResult.Binaries?.Node) { | ||
const supportedNodeVersion = await this.moduleManager.supportedNodeVersion(); | ||
const nodeStatus = this.status(scanResult.Binaries.Node.version, supportedNodeVersion); | ||
|
||
const node = { | ||
name: 'Node.js', | ||
binary: this.absolute(scanResult.Binaries.Node.path), | ||
version: scanResult.Binaries.Node.version, | ||
status: nodeStatus, | ||
}; | ||
|
||
commands.push(node); | ||
|
||
if (nodeStatus !== 'compatible') { | ||
instructions.push(trimmed` | ||
| Incompatible Node.js version detected: ${ scanResult.Binaries.Node.version ?? 'not found' }. | ||
| Before proceeding, install a recent Node.js Long-Term Support (LTS) version, e.g. ${ supportedNodeVersion }. | ||
`); | ||
} | ||
} | ||
|
||
let preferredPackageManager: CommandLineToolInfo; | ||
|
||
if (scanResult.Binaries?.pnpm) { | ||
const pnpm: CommandLineToolInfo = { | ||
name: 'pnpm', | ||
binary: this.absolute(scanResult.Binaries.pnpm.path), | ||
version: scanResult.Binaries.pnpm.version, | ||
status: 'compatible', | ||
}; | ||
|
||
commands.push(pnpm); | ||
|
||
if (this.fileExists(rootDirectory, 'pnpm-lock.yaml')) { | ||
preferredPackageManager = pnpm; | ||
} | ||
} | ||
|
||
if (scanResult.Binaries?.yarn) { | ||
const yarn: CommandLineToolInfo = { | ||
name: 'Yarn', | ||
binary: this.absolute(scanResult.Binaries.yarn.path), | ||
version: scanResult.Binaries.yarn.version, | ||
status: 'compatible', | ||
}; | ||
|
||
commands.push(yarn); | ||
|
||
if (this.fileExists(rootDirectory, 'yarn.lock')) { | ||
preferredPackageManager = yarn; | ||
} | ||
} | ||
|
||
if (scanResult.Binaries?.npm) { | ||
const npm: CommandLineToolInfo = { | ||
name: 'npm', | ||
binary: this.absolute(scanResult.Binaries.npm.path), | ||
version: scanResult.Binaries.npm.version, | ||
status: 'compatible', | ||
}; | ||
|
||
commands.push(npm); | ||
|
||
if (! preferredPackageManager) { | ||
preferredPackageManager = npm; | ||
} | ||
} | ||
|
||
instructions.push(trimmed` | ||
| You are acting as a coding assistant within a JavaScript/TypeScript project. | ||
| 1. Use: ${ preferredPackageManager.binary } to install any Node.js packages or run npm scripts (${ preferredPackageManager.name } detected at this path). | ||
| 2. Always use the full command, e.g., \`${ preferredPackageManager.binary } install <package-name>\` or \`${ preferredPackageManager.binary } run <script-name>\`. | ||
| 3. Always provide the correct command explicitly in your response. Do not mix commands from multiple package managers. | ||
|`); | ||
|
||
if (scanResult.Utilities?.Git) { | ||
commands.push({ | ||
name: 'Git', | ||
binary: this.absolute(scanResult.Utilities.Git.path), | ||
version: scanResult.Utilities.Git.version, | ||
status: 'compatible', | ||
}); | ||
|
||
instructions.push(trimmed` | ||
| This project uses Git. When making any code changes, always ensure that: | ||
| 1. You create and switch to a new, uniquely named Git branch for the changes created off the latest version of the main branch (e.g., \`main\` or \`master\`) | ||
| 2. If there are any uncommitted changes in the working directory, prompt the user to review and commit their work before proceeding. | ||
| 3. Always describe the branch purpose and your planned actions before starting, then confirm with the user." | ||
|`); | ||
} | ||
|
||
if (scanResult.Languages?.Java) { | ||
commands.push({ | ||
name: 'Java', | ||
binary: this.absolute(scanResult.Languages.Java.path), | ||
version: scanResult.Languages.Java.version, | ||
status: 'compatible', // todo: check the actual version against the expected one | ||
}); | ||
} | ||
|
||
const nextSteps: NextStepWithToolCall[] = []; | ||
|
||
nextSteps.push({ | ||
action: 'call_tool', | ||
toolName: ProjectAnalyzeDependenciesController.toolName, | ||
reason: 'Determine what Serenity/JS packages you need to install', | ||
}, { | ||
action: 'call_tool', | ||
toolName: ListCapabilitiesController.toolName, | ||
reason: 'Learn about available Serenity/JS capabilities', | ||
}, { | ||
action: 'call_tool', | ||
toolName: ProjectConfigurePlaywrightTestController.toolName, | ||
reason: 'Configure Playwright to use Serenity/JS', | ||
}); | ||
|
||
const PATH = commands.reduce((acc, tool) => tool.binary ? `${ Path.from(tool.binary).directory().value }:${ acc }` : acc, process.env.PATH ?? ''); | ||
const JAVA_HOME = scanResult.Languages?.Java?.path ? Path.from(scanResult.Languages.Java.path).directory().directory().value : (process.env.JAVA_HOME ?? ''); | ||
|
||
const environmentVariables = { | ||
...process.env, | ||
PATH, | ||
JAVA_HOME, | ||
} | ||
|
||
instructions.push(trimmed` | ||
| When invoking command line tools, set the following environment variables: | ||
| \`\`\` | ||
| PATH=${PATH} | ||
| \`\`\` | ||
|` | ||
); | ||
|
||
return { | ||
result: { | ||
status: commands.every(tool => tool.status === 'compatible') ? 'compatible' : 'incompatible', | ||
commands, | ||
environmentVariables, | ||
}, | ||
instructions, | ||
nextSteps, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RE #2985
Notes