Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

Copy link

qltysh bot commented Jul 29, 2025

❌ 4 blocking issues (4 total)

Tool Category Rule Count
qlty Structure Function with high complexity (count = 18): pageElementFromLocator 2
qlty Structure Function with many returns (count = 10): testRunner 1
qlty Duplication Found 21 lines of similar code in 2 locations (mass = 84) 1

Core tests migrated from ts-node to tsx to enable testing the new functionality
Copy link

qltysh bot commented Aug 20, 2025

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
Path File Coverage Δ Indirect
packages/core/src/errors/ErrorOptions.ts 100.0
packages/core/src/events/AsyncOperationAborted.ts 13.8
packages/core/src/events/SceneBackgroundDetected.ts 12.9
packages/core/src/instance.ts 0.4
packages/core/src/io/FileSystem.ts 0.8
packages/core/src/io/loader/ModuleLoader.ts -4.0
packages/core/src/io/reflection/ValueInspector.ts 2.4
packages/core/src/model/Artifact.ts 8.2
packages/core/src/model/ArtifactDeserialiser.ts 83.9
packages/core/src/model/artifacts/Photo.ts -3.7
packages/core/src/screenplay/questions/expectations/ExpectationDetails.ts 1.5
packages/core/src/stage/StageManager.ts 1.4
packages/mcp/src/server/context/Imports.ts 100.0
packages/mcp/src/server/context/ScreenplayExecutionContext.ts 91.1
packages/mcp/src/server/context/ScreenplayTemplate.ts 100.0
packages/mcp/src/server/context/index.ts 100.0
packages/mcp/src/server/context/schematics.ts 100.0
packages/mcp/src/server/controllers/CapabilityController.ts 100.0
packages/mcp/src/server/controllers/Controller.ts 100.0
packages/mcp/src/server/controllers/ListCapabilitiesController.ts 100.0
packages/mcp/src/server/controllers/index.ts 100.0
packages/mcp/src/server/controllers/project/ProjectAnalyzeDependenciesController.ts 21.0
packages/mcp/src/server/controllers/project/ProjectAnalyzeRuntimeEnvironmentController.ts 24.4
packages/mcp/src/server/controllers/project/ProjectConfigurePackageJsonScriptsController.ts 41.7
packages/mcp/src/server/controllers/project/ProjectConfigurePlaywrightTestController.ts 21.6
packages/mcp/src/server/controllers/project/ProjectCreateExampleTestFileController.ts 31.2
packages/mcp/src/server/controllers/project/index.ts 100.0
packages/mcp/src/server/controllers/test-automation/TestAutomationController.ts 83.3
packages/mcp/src/server/controllers/test-automation/index.ts 100.0
packages/mcp/src/server/integration/PlaywrightBrowserConnection.ts 95.7
packages/mcp/src/server/schema.ts 100.0
🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

Comment on lines +168 to +205
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(' '));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many returns (count = 10): testRunner [qlty:return-statements]

Comment on lines +42 to +61
| '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'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 21 lines of similar code in 2 locations (mass = 84) [qlty:similar-code]

Comment on lines +193 to +256
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`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 18): pageElementFromLocator [qlty:function-complexity]

Comment on lines +108 to +267
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,
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 18): scanProjectRuntime [qlty:function-complexity]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant