From 943a5f64ab2c496ccc40830d45e3b780c2ea6e95 Mon Sep 17 00:00:00 2001 From: "Mathi Sree.R" Date: Sat, 26 Sep 2026 12:55:54 +0530 Subject: [PATCH 1/3] Add technology detection tests --- .../seleniumboot/migrator/AnalyzerTest.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/test/java/com/seleniumboot/migrator/AnalyzerTest.java b/src/test/java/com/seleniumboot/migrator/AnalyzerTest.java index 0c24e49..99e8825 100644 --- a/src/test/java/com/seleniumboot/migrator/AnalyzerTest.java +++ b/src/test/java/com/seleniumboot/migrator/AnalyzerTest.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -57,4 +59,53 @@ void unparsableSourceIsReportedNotThrown() { var report = new Analyzer().analyzeSource("class {{{"); assertEquals(1, report.unparsable().size()); } + + @Test + void detectsMavenAndTechnologies() throws Exception { + Path temp = Files.createTempDirectory("selenium-test"); + + Files.writeString(temp.resolve("pom.xml"), """ + + + + org.seleniumhq.selenium + selenium-java + 4.20.0 + + + org.testng + testng + 7.10.0 + + + org.junit.jupiter + junit-jupiter + 5.10.2 + + + io.github.bonigarcia + webdrivermanager + 5.8.0 + + + + """); + + var report = new Analyzer().analyze(temp); + + assertTrue(report.render().contains("Maven")); + assertTrue(report.render().contains("Selenium 4.20.0")); + assertTrue(report.render().contains("TestNG")); + assertTrue(report.render().contains("JUnit 5")); + assertTrue(report.render().contains("WebDriverManager")); + } + + @Test + void missingPomIsNotDetected() throws Exception { + Path temp = Files.createTempDirectory("selenium-test"); + + var report = new Analyzer().analyze(temp); + + assertTrue(report.render().contains("not detected")); + } } From decee05be8778a3f53b9bc051eb0ebcfb9b945cf Mon Sep 17 00:00:00 2001 From: "Mathi Sree.R" Date: Sat, 26 Sep 2026 13:09:40 +0530 Subject: [PATCH 2/3] Add technology detection --- .../com/seleniumboot/migrator/Analyzer.java | 128 +++++++++++++++--- 1 file changed, 111 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/seleniumboot/migrator/Analyzer.java b/src/main/java/com/seleniumboot/migrator/Analyzer.java index e6594dc..8dcb312 100644 --- a/src/main/java/com/seleniumboot/migrator/Analyzer.java +++ b/src/main/java/com/seleniumboot/migrator/Analyzer.java @@ -5,12 +5,15 @@ import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; -import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.Node; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import javax.xml.parsers.DocumentBuilderFactory; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -21,7 +24,6 @@ import static com.seleniumboot.migrator.Finding.Status.AUTO; import static com.seleniumboot.migrator.Finding.Status.MANUAL; -/** Read-only static analysis: parses Java sources and reports patterns that map onto Selenium Boot. */ public final class Analyzer { private final JavaParser parser = new JavaParser( @@ -32,87 +34,109 @@ public Report analyze(Path root) throws IOException { try (Stream s = Files.walk(root)) { files = s.filter(p -> p.toString().endsWith(".java")).sorted().toList(); } + List findings = new ArrayList<>(); int parsed = 0; List unparsable = new ArrayList<>(); + for (Path f : files) { var result = parser.parse(f); + if (result.getResult().isEmpty() || !result.isSuccessful()) { unparsable.add(root.relativize(f).toString()); continue; } + parsed++; scan(result.getResult().get(), root.relativize(f).toString(), findings); } - return new Report(files.size(), parsed, unparsable, findings); + + String technologies = detectTechnologies(root); + + return new Report(files.size(), parsed, unparsable, findings, technologies); } - /** Analyze a single pasted source string. */ public Report analyzeSource(String source) { var result = parser.parse(source); List findings = new ArrayList<>(); + if (result.getResult().isEmpty() || !result.isSuccessful()) { - return new Report(1, 0, List.of(""), findings); + return new Report(1, 0, List.of(""), findings, "not detected"); } + scan(result.getResult().get(), "", findings); - return new Report(1, 1, List.of(), findings); + + return new Report(1, 1, List.of(), findings, "not detected"); } private void scan(CompilationUnit cu, String file, List out) { - // MIG-001: ThreadLocal driver factory + cu.findAll(FieldDeclaration.class).forEach(fd -> { String type = fd.getElementType().toString(); + if (type.startsWith("ThreadLocal<") && type.contains("WebDriver")) { out.add(new Finding("MIG-001", AUTO, file, line(fd), "ThreadLocal", "Delete the driver factory; extend BaseTest (per-thread isolation is built in).")); } }); - // MIG-002: WebDriverManager.*.setup() + cu.findAll(NameExpr.class).stream() .filter(n -> n.getNameAsString().equals("WebDriverManager")) .forEach(n -> out.add(new Finding("MIG-002", AUTO, file, line(n), "WebDriverManager", "Delete; Selenium Manager fetches drivers automatically."))); - // MIG-003: WebDriverWait / ExpectedConditions + cu.findAll(ObjectCreationExpr.class).stream() .filter(o -> o.getType().getNameAsString().equals("WebDriverWait")) .forEach(o -> out.add(new Finding("MIG-003", MANUAL, file, line(o), "new WebDriverWait(...)", "Use $(locator) auto-wait or getWait(); review the condition by hand."))); + cu.findAll(NameExpr.class).stream() .filter(n -> n.getNameAsString().equals("ExpectedConditions")) .forEach(n -> out.add(new Finding("MIG-003", MANUAL, file, line(n), "ExpectedConditions", "Map to $(locator) auto-wait or a WaitEngine call."))); - // MIG-004: retry analyzer / annotation transformer + cu.findAll(ClassOrInterfaceDeclaration.class).forEach(c -> { + boolean retry = c.getImplementedTypes().stream() .anyMatch(t -> t.getNameAsString().equals("IRetryAnalyzer") || t.getNameAsString().equals("IAnnotationTransformer")); + if (retry) { out.add(new Finding("MIG-004", AUTO, file, line(c), c.getNameAsString(), "Delete; set retry.enabled in selenium-boot.yml or use @Retryable.")); } - // MIG-005: screenshot-on-failure listener - boolean listener = c.getImplementedTypes().stream().anyMatch(t -> t.getNameAsString().equals("ITestListener")); - boolean shots = c.findAll(NameExpr.class).stream().anyMatch(n -> n.getNameAsString().equals("TakesScreenshot")) + + boolean listener = c.getImplementedTypes().stream() + .anyMatch(t -> t.getNameAsString().equals("ITestListener")); + + boolean shots = c.findAll(NameExpr.class).stream() + .anyMatch(n -> n.getNameAsString().equals("TakesScreenshot")) || c.toString().contains("TakesScreenshot"); + if (listener && shots) { out.add(new Finding("MIG-005", AUTO, file, line(c), c.getNameAsString(), "Delete; failure screenshots are captured automatically.")); } }); - // MIG-014: hard-coded sleeps, MIG-016: implicit waits (flag only) + cu.findAll(MethodCallExpr.class).forEach(m -> { - if (m.getNameAsString().equals("sleep") && m.getScope().map(s -> s.toString().equals("Thread")).orElse(false)) { + + if (m.getNameAsString().equals("sleep") + && m.getScope().map(s -> s.toString().equals("Thread")).orElse(false)) { + out.add(new Finding("MIG-014", MANUAL, file, line(m), "Thread.sleep(...)", "Replace with an auto-waiting locator or getWait().")); } + if (m.getNameAsString().equals("implicitlyWait")) { out.add(new Finding("MIG-016", MANUAL, file, line(m), "implicitlyWait(...)", "Remove; mixing implicit and explicit waits causes flakiness.")); } }); - // MIG-015: custom DriverManager (flag only) + cu.findAll(ClassOrInterfaceDeclaration.class).stream() - .filter(c -> c.getNameAsString().endsWith("DriverManager") || c.getNameAsString().endsWith("DriverFactory")) + .filter(c -> c.getNameAsString().endsWith("DriverManager") + || c.getNameAsString().endsWith("DriverFactory")) .forEach(c -> out.add(new Finding("MIG-015", MANUAL, file, line(c), c.getNameAsString(), "Custom driver lifecycle: review, then replace with BaseTest."))); } @@ -120,4 +144,74 @@ private void scan(CompilationUnit cu, String file, List out) { private static int line(Node n) { return n.getBegin().map(p -> p.line).orElse(0); } + + private static String detectTechnologies(Path root) { + Path pom = root.resolve("pom.xml"); + + if (!Files.exists(pom)) { + return "not detected"; + } + + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document document = factory.newDocumentBuilder().parse(pom.toFile()); + + StringBuilder result = new StringBuilder("Maven"); + + NodeList dependencies = document.getElementsByTagName("dependency"); + + for (int i = 0; i < dependencies.getLength(); i++) { + Element dependency = (Element) dependencies.item(i); + + String groupId = getText(dependency, "groupId"); + String artifactId = getText(dependency, "artifactId"); + String version = getText(dependency, "version"); + + if ("org.seleniumhq.selenium".equals(groupId) + && "selenium-java".equals(artifactId)) { + result.append(", Selenium ").append(version); + } + + if ("org.testng".equals(groupId)) { + result.append(", TestNG"); + } + + if ("junit".equals(groupId) && "junit".equals(artifactId)) { + result.append(", JUnit 4"); + } + + if ("org.junit.jupiter".equals(groupId)) { + result.append(", JUnit 5"); + } + + if ("io.github.bonigarcia".equals(groupId) + && "webdrivermanager".equals(artifactId)) { + result.append(", WebDriverManager"); + } + + if ("com.aventstack".equals(groupId)) { + result.append(", ExtentReports"); + } + + if ("io.qameta.allure".equals(groupId)) { + result.append(", Allure"); + } + } + + return result.toString(); + + } catch (Exception e) { + return "Maven"; + } + } + + private static String getText(Element parent, String tagName) { + NodeList nodes = parent.getElementsByTagName(tagName); + + if (nodes.getLength() == 0) { + return ""; + } + + return nodes.item(0).getTextContent().trim(); + } } From 44b807e7b7f0c656f2ef0b9e9fb040d5b35fa445 Mon Sep 17 00:00:00 2001 From: "Mathi Sree.R" Date: Sat, 26 Sep 2026 13:12:43 +0530 Subject: [PATCH 3/3] Add detected technologies to report --- src/main/java/com/seleniumboot/migrator/Report.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/seleniumboot/migrator/Report.java b/src/main/java/com/seleniumboot/migrator/Report.java index 6020c0e..8052120 100644 --- a/src/main/java/com/seleniumboot/migrator/Report.java +++ b/src/main/java/com/seleniumboot/migrator/Report.java @@ -4,7 +4,7 @@ import java.util.Map; import java.util.TreeMap; -public record Report(int filesFound, int filesParsed, List unparsable, List findings) { +public record Report(int filesFound, int filesParsed, List unparsable, List findings, String technologies) { public long count(Finding.Status s) { return findings.stream().filter(f -> f.status() == s).count(); @@ -22,6 +22,7 @@ public String render() { StringBuilder sb = new StringBuilder("Selenium Boot Migration Analysis\n\n"); sb.append(String.format("Files found: %d%n", filesFound)); sb.append(String.format("Files parsed: %d%n", filesParsed)); + sb.append(String.format("%nDetected technologies: %s%n", technologies)); Map byRule = new TreeMap<>(); findings.forEach(f -> byRule.merge(f.ruleId(), 1L, Long::sum)); byRule.forEach((r, n) -> sb.append(String.format("%-25s %d%n", r + ":", n)));