Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ build/
.vscode/

### Mac OS ###
.DS_Store
.DS_Store

# MCP AI Test logs and reports
mcp_testlog/
mcp_testreport/
35 changes: 33 additions & 2 deletions config/mcp-config.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
# Output directory for generated tests
output.dir=src/test/ai_generated_tests/k11softwaresolutions
# Directory for AI prompt templates
prompt.dir=mcp-prompts/k11softwaresolutions
# Web URL for AI-generated tests
weburl=https://k11softwaresolutions-platform.vercel.app/
username=testuser
password=testpass

# === MCPTestWorkflow properties (no prefix) ===
scenario=Login to the dashboard and verify the dashboard is visible
classNameBase=GeneratedPlaywrightLogin
ai.packageName=org.k11techlab.framework_unittests.ai_generated.k11softwaresolutions
# Java package for ConfigurationManager class
configmanager.package=org.k11techlab.framework.selenium.webuitestengine.configManager
# Directory for MCP test logs
log.dir=mcp_testlog

maven.test.dir=src/test/java/org/k11techlab/framework_unittests/ai_generated/k11softwaresolutions

review.prompt=Review the following Java TestNG test class for correctness, best practices, and possible improvements. Provide a concise summary and suggestions.\n\n
report.src=test-output/emailable-report.html
# Path to Maven executable for test automation
maven.cmd.path=C:/Program Files/maven-1.0.2/mvn/bin/mvn.cmd
# Output directory for generated tests
output.dir=src/test/java/org/k11techlab/framework_unittests/ai_generated/k11softwaresolutions


# Default engine for MCP test generation (selenium or playwright)
engine=selenium
# Default package name for generated tests
Expand All @@ -18,3 +37,15 @@ mongo.db=mcpdb
mongo.collection=context



# === k11softwaresolutions app-specific properties ===
k11softwaresolutions.weburl=https://k11softwaresolutions-platform.vercel.app/
# Page object prompt config
k11softwaresolutions.prompt.pagedir=mcp-prompts/k11softwaresolutions/pages
k11softwaresolutions.prompt.pages=pageobject_creation_prompt_multi.txt
# Test generation prompt config
k11softwaresolutions.prompt.testdir=mcp-prompts/k11softwaresolutions/tests
k11softwaresolutions.prompt.logintest=logintest_creation_prompt.txt

# Framework standards prompt for test generation
k11softwaresolutions.prompt.framework=mcp-prompts/framework_standards_prompt.txt
17 changes: 17 additions & 0 deletions mcp_prompts/framework_standards_prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Playwright Framework Standards Prompt

- All Playwright test classes must extend `BasePlaywrightTest`.
- Use `PlaywrightManager.getInstance()` to obtain the browser, context, and page.
- Do not use `Playwright.create()` or direct browser/page instantiation in test classes.
- Use `@BeforeClass` and `@AfterClass` for setup and teardown, delegating to `BasePlaywrightTest` where possible.
- Example test method:

@Test
public void testExample() {
Page page = PlaywrightManager.getInstance().getPage();
// test steps...
}

- Use the Page Object Model for all page interactions.
- Follow Java and TestNG best practices for naming and structure.
- All test code should be readable, maintainable, and DRY.
17 changes: 17 additions & 0 deletions mcp_prompts/k11softwaresolutions/logintest_prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Generate a complete, compilable Java TestNG test class using Playwright for Java.
Package: {packageName}
Class: {className}
MUST include:
- All required imports (Playwright, TestNG, ConfigurationManager, etc).
- A @BeforeClass setUp() method that launches Playwright, a Chromium browser in headless mode, and creates a Page. Store Playwright, Browser, and Page as fields.
- A @Test method that:
- Reads weburl, username, and password using ConfigurationManager.getBundle().getPropertyValue('weburl'), etc.
- Navigates to the weburl.
- Fills the username and password fields and clicks the login button (use generic selectors if not specified).
- Asserts that the dashboard is visible after login (e.g., page.isVisible('text=Dashboard')).
- A @AfterClass tearDown() method that closes the browser and Playwright.
- Output ONLY Java code, no markdown.
- The code must be complete and not truncated.

Scenario:
{scenario}
18 changes: 18 additions & 0 deletions mcp_prompts/k11softwaresolutions/pageobject_creation_prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Page Object Creation Prompt Example

You are an AI assistant that generates Playwright Page Object Model (POM) Java classes. Use the following JSON structure as input:

{
"className": "HomePage",
"fields": {
"isDashboardVisible": {"id": "Dashboard", "method": "isVisible"},
"clickServiceLink": {"id": "service", "text": "Service", "method": "click"},
"clickLoginMenu": {"id": "loginMenu", "text": "Login", "method": "click"},
"clickRegisterMenu": {"id": "registerMenu", "text": "Register", "method": "click"}
}
}

- Use id as the first preference, then name, then text for selectors.
- Each field should generate a method in the Java class.
- Use Playwright's Page API for actions (fill, click, isVisible, etc).
- Output only valid Java code for the page object class.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Page Object Creation Prompt for HomePage and LoginPage

{
"pageObjects": [
{
"className": "HomePage",
"fields": {
"isDashboardVisible": {"id": "Dashboard", "method": "isVisible"},
"clickServiceLink": {"id": "service", "text": "Service", "method": "click"},
"clickLoginMenu": {"id": "loginMenu", "text": "Login", "method": "click"},
"clickRegisterMenu": {"id": "registerMenu", "text": "Register", "method": "click"}
}
},
{
"className": "LoginPage",
"fields": {
"enterUsername": {"id": "username", "name": "username", "text": "Username", "method": "fill"},
"enterPassword": {"id": "password", "name": "password", "text": "Password", "method": "fill"},
"clickLogin": {"id": "login", "name": "login", "text": "Login", "method": "click"}
}
}
]
}

- Use id as the first preference, then name, then text for selectors.
- Each field should generate a method in the Java class.
- Use Playwright's Page API for actions (fill, click, isVisible, etc).
- Output only valid Java code for the page object classes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Login Test Generation Prompt

{
"testScenario": "Login to the dashboard and verify the dashboard is visible",
"testClassName": "GeneratedPlaywrightLoginTest",
"pageObjects": ["HomePage", "LoginPage"],
"steps": [
"Navigate to the login page",
"Enter username and password",
"Click the login button",
"Verify the dashboard is visible"
]
}

- Use the provided page objects and methods.
- Use TestNG for the test class structure.
- Use Playwright's Page API for browser actions.
- Output only valid Java code for the test class.
4 changes: 3 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
<version>2.20</version>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
<includes>
<include>org/k11techlab/framework_unittests/webUITests/wikipedia/**</include>
</includes>
</configuration>
</plugin>
<plugin>
Expand Down Expand Up @@ -64,7 +67,6 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.44.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.javaparser</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public void start(int port) throws IOException {
server.createContext("/mcp/correct-code", new CorrectCodeHandler(new OpenAIClient()));
server.createContext("/mcp/generate-and-run-selenium-test", new GenerateAndRunTestHandler());
server.createContext("/mcp/generate-and-run-playwright-test", new GenerateAndRunTestHandler());
server.createContext("/mcp/generate-page-object", new GeneratePageObjectHandler());

server.setExecutor(null);
server.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public GenerateAndRunTestHandler() {

// Playwright wiring
this.playwrightGenerator = new PlaywrightTestGenerator(openAI);
this.playwrightWorkflow = new PlaywrightWorkflow(playwrightGenerator, openAI, 2);
this.playwrightWorkflow = new PlaywrightWorkflow();
}


Expand All @@ -55,7 +55,7 @@ public void handle(HttpExchange exchange) throws IOException {

String packageName = (configPackage != null && !configPackage.isBlank())
? configPackage.trim()
: "org.k11techlab.framework_unittests.ai_generated";
: "org.k11techlab.framework_unittests.ai_generated.k11softwaresolutions";

String classBase = "GeneratedTest_" + System.currentTimeMillis();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.k11techlab.framework.ai.mcp.handlers;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import org.json.JSONObject;

public class GeneratePageObjectHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equalsIgnoreCase("POST")) {
exchange.sendResponseHeaders(405, -1);
return;
}
String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
JSONObject req = new JSONObject(body);
// If promptFile is provided, load JSON from file
if (req.has("promptFile")) {
String promptFile = req.getString("promptFile");
try {
java.nio.file.Path promptPath = java.nio.file.Paths.get("mcp-prompts", promptFile);
String promptContent = new String(java.nio.file.Files.readAllBytes(promptPath), StandardCharsets.UTF_8);
int start = promptContent.indexOf('{');
int end = promptContent.indexOf('}', start);
while (end != -1 && end + 1 < promptContent.length() && promptContent.charAt(end + 1) != '\n') {
end = promptContent.indexOf('}', end + 1);
}
if (start != -1 && end != -1) {
req = new JSONObject(promptContent.substring(start, end + 1));
}
} catch (Exception e) {
exchange.sendResponseHeaders(500, -1);
return;
}
}
StringBuilder code = new StringBuilder();
if (req.has("pageObjects")) {
// Multiple page objects
org.json.JSONArray arr = req.getJSONArray("pageObjects");
for (int i = 0; i < arr.length(); i++) {
JSONObject po = arr.getJSONObject(i);
code.append(generatePageObjectCode(po));
code.append("\n\n");
}
} else if (req.has("className") && req.has("fields")) {
code.append(generatePageObjectCode(req));
} else {
// Invalid input
exchange.sendResponseHeaders(400, -1);
return;
}
byte[] resp = code.toString().getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8");
exchange.sendResponseHeaders(200, resp.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(resp);
}
}

private String generatePageObjectCode(JSONObject req) {
String className = req.optString("className", "LoginPage");
JSONObject fields = req.optJSONObject("fields");
StringBuilder code = new StringBuilder();
code.append("public class ").append(className).append(" {\n");
code.append(" private final com.microsoft.playwright.Page page;\n\n");
code.append(" public ").append(className).append("(com.microsoft.playwright.Page page) { this.page = page; }\n\n");
for (String key : fields.keySet()) {
JSONObject field = fields.getJSONObject(key);
String method = field.optString("method", "fill");
String id = field.optString("id", "");
String name = field.optString("name", "");
String text = field.optString("text", "");
code.append(" public void ").append(key).append("(String value) {\n");
code.append(" String selector = null;\n");
if (!id.isEmpty()) code.append(" if (page.querySelector(\"#" + id + "\") != null) selector = \"#" + id + "\";\n");
if (!name.isEmpty()) code.append(" else if (page.querySelector(\"input[name='" + name + "']\") != null) selector = \"input[name='" + name + "']\";\n");
if (!text.isEmpty()) code.append(" else if (page.querySelector(\"text=" + text + "\") != null) selector = \"text=" + text + "\";\n");
code.append(" if (selector != null) page.").append(method).append("(selector, value);\n");
code.append(" }\n\n");
}
code.append("}\n");
return code.toString();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.k11techlab.framework.ai.mcp.workflow.playwright;

import org.k11techlab.framework.ai.openai.OpenAIClient;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;

public class GeneratedTestReviewerAndExecutor {
public static void main(String[] args) {
// Path to the generated test file
String testFilePath = "src/test/ai_generated_tests/k11softwaresolutions/LoginFunctionalityTest.java";
String testClassName = "org.k11techlab.framework_unittests.ai_generated.LoginFunctionalityTest";

// 1. Review the generated test using OpenAI
String code = null;
try {
code = new String(Files.readAllBytes(Paths.get(testFilePath)));
} catch (IOException e) {
System.err.println("Failed to read test file: " + e.getMessage());
return;
}
OpenAIClient openAI = new OpenAIClient();
String reviewPrompt = "Review the following Java TestNG test class for correctness, best practices, and possible improvements. Provide a concise summary and suggestions.\n\n" + code;
String review = openAI.generateResponse(reviewPrompt);
System.out.println("\n--- AI Review of Generated Test ---\n");
System.out.println(review);

// 2. Copy the generated test to src/test/java for Maven discovery
System.out.println("\n--- Copying Generated Test to src/test/java for Maven discovery ---\n");
String destDir = "src/test/java/org/k11techlab/framework_unittests/ai_generated";
java.io.File destDirFile = new java.io.File(destDir);
if (!destDirFile.exists()) destDirFile.mkdirs();
java.io.File destFile = new java.io.File(destDirFile, "LoginFunctionalityTest.java");
try {
Files.copy(Paths.get(testFilePath), destFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copied to: " + destFile.getAbsolutePath());
} catch (IOException e) {
System.err.println("Failed to copy test file: " + e.getMessage());
return;
}

// 3. Execute the test using Maven (TestNG)
System.out.println("\n--- Executing Generated Test with Maven ---\n");
try {
// Read Maven path from mcp-config.properties
java.util.Properties props = new java.util.Properties();
try (java.io.FileInputStream fis = new java.io.FileInputStream("config/mcp-config.properties")) {
props.load(fis);
}
String mavenCmd = props.getProperty("maven.cmd.path", "mvn");
ProcessBuilder pb = new ProcessBuilder(
mavenCmd, "-Dtest=org.k11techlab.framework_unittests.ai_generated.LoginFunctionalityTest", "test"
);
pb.redirectErrorStream(true);
Process process = pb.start();
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int exitCode = process.waitFor();
System.out.println("\nTest execution finished with exit code: " + exitCode);
} catch (Exception e) {
System.err.println("Failed to execute test: " + e.getMessage());
}
}
}
Loading
Loading