diff --git a/src/main/java/com/seleniumboot/migrator/Analyzer.java b/src/main/java/com/seleniumboot/migrator/Analyzer.java index 7772430..cb2b85d 100644 --- a/src/main/java/com/seleniumboot/migrator/Analyzer.java +++ b/src/main/java/com/seleniumboot/migrator/Analyzer.java @@ -6,7 +6,6 @@ import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; 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; @@ -15,10 +14,11 @@ import com.github.javaparser.ast.expr.FieldAccessExpr; import com.github.javaparser.ast.expr.ThisExpr; import com.github.javaparser.ast.Node; -import com.github.javaparser.ast.stmt.ExpressionStmt; -import com.github.javaparser.ast.stmt.Statement; -import com.github.javaparser.ast.expr.VariableDeclarationExpr; +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; @@ -30,7 +30,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 static final Set DRIVER_LIFECYCLE_ANNOTATIONS = Set.of( @@ -45,110 +44,110 @@ 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<>(); LocatorStats locatorStats = new LocatorStats(); 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, locatorStats); } - List detectedTechnologies = BuildFileAnalyzer.detect(root); - return new Report(files.size(), parsed, unparsable, findings, detectedTechnologies, - recognizedTechnologies(detectedTechnologies), locatorStats.counts()); + + 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<>(); - LocatorStats locatorStats = new LocatorStats(); + 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, locatorStats); - return new Report(1, 1, List.of(), findings, List.of(), List.of(), locatorStats.counts()); + + scan(result.getResult().get(), "", findings); + + return new Report(1, 1, List.of(), findings, "not detected"); } - private void scan(CompilationUnit cu, String file, List out, LocatorStats locatorStats) { - cu.findAll(MethodCallExpr.class).forEach(locatorStats::scan); + private void scan(CompilationUnit cu, String file, List out) { - // MIG-010: page-object candidates with a WebDriver constructor - cu.findAll(ClassOrInterfaceDeclaration.class).stream() - .filter(c -> !c.isInterface()) - .filter(c -> c.getConstructors().stream().anyMatch(Analyzer::hasWebDriverParameter)) - .forEach(c -> out.add(new Finding("MIG-010", MANUAL, file, line(c), c.getNameAsString(), - "Review as a page object; Selenium Boot documents extending BasePage."))); - // MIG-011: @FindBy fields - cu.findAll(FieldDeclaration.class).forEach(fd -> { - if (fd.getAnnotations().stream().anyMatch(a -> isAnnotation(a.getNameAsString(), "FindBy"))) { - fd.getVariables().forEach(v -> out.add(new Finding("MIG-011", MANUAL, file, line(fd), - v.getNameAsString(), - "Selenium Boot documents By locator fields, not @FindBy; review before mapping to BasePage."))); - } - }); - // MIG-012: PageFactory initialization - cu.findAll(MethodCallExpr.class).stream() - .filter(m -> m.getNameAsString().equals("initElements")) - .filter(m -> m.getScope().map(s -> s.toString().endsWith("PageFactory")).orElse(false)) - .forEach(m -> out.add(new Finding("MIG-012", MANUAL, file, line(m), "PageFactory.initElements(...)", - "PageFactory is not documented as a Selenium Boot pattern; review page initialization."))); - // 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(ExpressionStmt.class).stream() - .filter(Analyzer::containsLegacyWait) - .forEach(statement -> out.add(new Finding("MIG-003", MANUAL, file, line(statement), - "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."))); - // MIG-004: retry analyzer / annotation transformer + + 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."))); + 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."))); // MIG-017: driver lifecycle managed in TestNG or JUnit setup/teardown methods @@ -224,65 +223,73 @@ private static int line(Node n) { return n.getBegin().map(p -> p.line).orElse(0); } - private static boolean hasWebDriverParameter(ConstructorDeclaration constructor) { - return constructor.getParameters().stream() - .anyMatch(p -> isWebDriverType(p.getType().asString())); - } + private static String detectTechnologies(Path root) { + Path pom = root.resolve("pom.xml"); - private static boolean isWebDriverType(String type) { - return type.equals("WebDriver") || type.endsWith(".WebDriver"); - } + if (!Files.exists(pom)) { + return "not detected"; + } - private static boolean isAnnotation(String name, String simpleName) { - return name.equals(simpleName) || name.endsWith("." + simpleName); - } + 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); - private static List recognizedTechnologies(List detectedTechnologies) { - boolean testNg = false; - boolean junit4 = false; - boolean junit5 = false; - boolean webDriverManager = false; - boolean extentReports = false; - boolean allure = false; - String seleniumVersion = null; - - for (String detected : detectedTechnologies) { - if (!detected.startsWith("Dependency: ")) continue; - - String[] coordinates = detected.substring("Dependency: ".length()).split(":", -1); - if (coordinates.length < 2) continue; - - String groupId = coordinates[0]; - String artifactId = coordinates[1]; - String version = coordinates.length > 2 ? coordinates[2] : ""; - - if (groupId.equals("org.seleniumhq.selenium") && artifactId.equals("selenium-java")) { - seleniumVersion = version; - } else if (groupId.equals("org.testng") && artifactId.equals("testng")) { - testNg = true; - } else if (groupId.equals("junit") && artifactId.equals("junit")) { - junit4 = true; - } else if (groupId.equals("org.junit.jupiter")) { - junit5 = true; - } else if (groupId.equals("io.github.bonigarcia") && artifactId.equals("webdrivermanager")) { - webDriverManager = true; - } else if (groupId.equals("com.aventstack") && artifactId.equals("extentreports")) { - extentReports = true; - } else if (groupId.equals("io.qameta.allure")) { - allure = true; + 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); - List recognized = new ArrayList<>(); - if (seleniumVersion != null) { - recognized.add(seleniumVersion.isBlank() ? "Selenium" : "Selenium " + seleniumVersion); + if (nodes.getLength() == 0) { + return ""; } - if (testNg) recognized.add("TestNG"); - if (junit4) recognized.add("JUnit 4"); - if (junit5) recognized.add("JUnit 5"); - if (webDriverManager) recognized.add("WebDriverManager"); - if (extentReports) recognized.add("ExtentReports"); - if (allure) recognized.add("Allure"); - return List.copyOf(recognized); + + return nodes.item(0).getTextContent().trim(); } } diff --git a/src/main/java/com/seleniumboot/migrator/Report.java b/src/main/java/com/seleniumboot/migrator/Report.java index 9a3cc25..6200fb5 100644 --- a/src/main/java/com/seleniumboot/migrator/Report.java +++ b/src/main/java/com/seleniumboot/migrator/Report.java @@ -5,19 +5,7 @@ import java.util.Map; import java.util.TreeMap; - -public record Report(int filesFound, int filesParsed, List unparsable, List findings, - List detectedTechnologies, List recognizedTechnologies, - Map locatorCounts) { - - public Report(int filesFound, int filesParsed, List unparsable, List findings) { - this(filesFound, filesParsed, unparsable, findings, List.of(), List.of(), Map.of()); - } - - public Report(int filesFound, int filesParsed, List unparsable, List findings, - List detectedTechnologies) { - this(filesFound, filesParsed, unparsable, findings, detectedTechnologies, List.of(), Map.of()); - } +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(); @@ -55,26 +43,10 @@ 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("\nDetected technologies\n"); - if (detectedTechnologies.isEmpty()) { - sb.append(" No supported build descriptor found\n"); - } else { - detectedTechnologies.forEach(technology -> sb.append(" ").append(technology).append('\n')); - } - sb.append("\nRecognized technologies\n"); - if (recognizedTechnologies.isEmpty()) { - sb.append(" No supported test technologies detected\n"); - } else { - sb.append(" ").append(String.join(", ", recognizedTechnologies)).append('\n'); - } - sb.append("\nLocator usage:\n"); - if (locatorCounts.isEmpty()) { - sb.append(" None detected\n"); - } else { - locatorCounts.forEach((locator, count) -> - sb.append(String.format(" %-22s %d%n", locator + ":", count))); - } - ruleCounts().forEach((r, n) -> sb.append(String.format("%-40s %d%n", ruleLabel(r) + ":", n))); + 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("%-40s %d%n", ruleLabel(r) + ":", n))); sb.append(String.format("%nMaps cleanly: %d%n", count(Finding.Status.AUTO))); sb.append(String.format("Manual review required: %d%n", count(Finding.Status.MANUAL))); sb.append(String.format("Unparsable files: %d%n", unparsable.size())); diff --git a/src/test/java/com/seleniumboot/migrator/AnalyzerTest.java b/src/test/java/com/seleniumboot/migrator/AnalyzerTest.java index d8d3600..5bd48d9 100644 --- a/src/test/java/com/seleniumboot/migrator/AnalyzerTest.java +++ b/src/test/java/com/seleniumboot/migrator/AnalyzerTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -139,58 +138,6 @@ void unparsableSourceIsReportedNotThrown() { assertEquals(1, report.unparsable().size()); } - @Test - void detectsGroovyGradleBuildAndDependencies() throws Exception { - String output = new Analyzer().analyze(fixture("gradle-groovy")).render(); - assertTrue(output.contains("Detected technologies")); - assertTrue(output.contains("Build system: Gradle (Groovy DSL)")); - assertTrue(output.contains("Dependency: org.seleniumhq.selenium:selenium-java:4.21.0")); - assertTrue(output.contains("Dependency: org.testng:testng:7.10.2")); - } - - @Test - void detectsKotlinGradleBuildAndDependencies() throws Exception { - String output = new Analyzer().analyze(fixture("gradle-kotlin")).render(); - assertTrue(output.contains("Detected technologies")); - assertTrue(output.contains("Build system: Gradle (Kotlin DSL)")); - assertTrue(output.contains("Dependency: org.seleniumhq.selenium:selenium-java:4.21.0")); - assertTrue(output.contains("Dependency: org.junit.jupiter:junit-jupiter:5.10.2")); - } - - @Test - void reportsMavenDependenciesInTheSameSection() throws Exception { - String output = new Analyzer().analyze(fixture("maven")).render(); - assertTrue(output.contains("Detected technologies")); - assertTrue(output.contains("Build system: Maven")); - assertTrue(output.contains("Dependency: org.seleniumhq.selenium:selenium-java:4.21.0")); - } - - @Test - void reportsMalformedMavenBuildAndContinuesAnalysis(@TempDir Path root) throws Exception { - Files.writeString(root.resolve("pom.xml"), ""); - Files.writeString(root.resolve("build.gradle"), "implementation 'org.example:sample:1.0'"); - - String output = new Analyzer().analyze(root).render(); - - assertTrue(output.contains("Build system: Maven (could not parse pom.xml)")); - assertTrue(output.contains("Build system: Gradle (Groovy DSL)")); - assertTrue(output.contains("Dependency: org.example:sample:1.0")); - } - - @Test - void ignoresBuildFilesInGeneratedAndVendorDirectories(@TempDir Path root) throws Exception { - for (String directory : List.of("target", "build", ".gradle", "node_modules")) { - Path ignored = Files.createDirectories(root.resolve(directory)); - Files.writeString(ignored.resolve("build.gradle"), "implementation 'org.example:sample:1.0'"); - } - - String output = new Analyzer().analyze(root).render(); - - assertFalse(output.contains("Build system: Gradle")); - assertFalse(output.contains("Dependency: org.example:sample:1.0")); - assertTrue(output.contains("No supported build descriptor found")); - } - @Test void detectsMavenAndTechnologies() throws Exception { Path temp = Files.createTempDirectory("selenium-test"); @@ -213,26 +160,11 @@ void detectsMavenAndTechnologies() throws Exception { junit-jupiter 5.10.2 - - junit - junit - 4.13.2 - io.github.bonigarcia webdrivermanager 5.8.0 - - com.aventstack - extentreports - 5.1.1 - - - io.qameta.allure - allure-testng - 2.27.0 - """); @@ -244,162 +176,6 @@ void detectsMavenAndTechnologies() throws Exception { assertTrue(report.render().contains("TestNG")); assertTrue(report.render().contains("JUnit 5")); assertTrue(report.render().contains("WebDriverManager")); - assertEquals(List.of("Selenium 4.20.0", "TestNG", "JUnit 4", "JUnit 5", - "WebDriverManager", "ExtentReports", "Allure"), report.recognizedTechnologies()); - } - - @Test - void recognizesOnlyDirectDependenciesAndDeduplicatesModules(@TempDir Path root) throws Exception { - Files.writeString(root.resolve("pom.xml"), """ - - - - - org.junit.jupiter - junit-jupiter - 5.10.2 - - - - - - org.testng - testng - 7.10.0 - - - - - - - - com.aventstack - extentreports - 5.1.1 - - - - - - - """); - - Path module = Files.createDirectories(root.resolve("module")); - Files.writeString(module.resolve("pom.xml"), """ - - - - org.testng - testng - 7.10.0 - - - org.seleniumhq.selenium - selenium-java - 4.20.0 - - - - """); - - var report = new Analyzer().analyze(root); - - assertEquals(List.of("Selenium 4.20.0", "TestNG"), report.recognizedTechnologies()); - assertEquals(1, report.detectedTechnologies().stream() - .filter(dependency -> dependency.equals("Dependency: org.testng:testng:7.10.0")) - .count()); - } - - @Test - void allAutoFindingsHaveFullConfidence() { - var report = new Analyzer().analyzeSource(""" - class AutoOnly { - ThreadLocal driver = new ThreadLocal<>(); - } - """); - - assertEquals(100, report.estimatedConfidence()); - } - - @Test - void allManualFindingsDoNotDropConfidenceToZero() { - var report = new Analyzer().analyzeSource(""" - class Page { - void waitForElement() { - new WebDriverWait(driver, Duration.ofSeconds(5)) - .until(ExpectedConditions.visibilityOfElementLocated(By.id("x"))); - } - } - """); - - assertEquals(50, report.estimatedConfidence()); - } - - @Test - void mixedAutoAndManualFilesProduceWeightedConfidence() { - var report = new Report( - 2, - 2, - List.of(), - List.of( - new Finding("AUTO", Finding.Status.AUTO, "Auto.java", 1, "", ""), - new Finding("MANUAL", Finding.Status.MANUAL, "Manual.java", 1, "", "") - ) - ); - - assertEquals(75, report.estimatedConfidence()); - } - - @Test - void unparsableFilesLowerConfidence() { - var report = new Report( - 2, - 1, - List.of("Broken.java"), - List.of( - new Finding("AUTO", Finding.Status.AUTO, "Auto.java", 1, "", "") - ) - ); - - assertEquals(50, report.estimatedConfidence()); - } - - @Test - void mig003EmitsOneFindingPerWaitStatement() { - var report = new Analyzer().analyzeSource(""" - class Page { - void waitForElement() { - new WebDriverWait(driver, Duration.ofSeconds(5)) - .until(ExpectedConditions.visibilityOfElementLocated(By.id("x"))); - } - } - """); - - assertEquals(1, report.findings().stream() - .filter(finding -> finding.ruleId().equals("MIG-003")) - .count()); - } - - @Test - void rejectsDoctypeInMavenBuildFile(@TempDir Path root) throws Exception { - Path secret = Files.writeString(root.resolve("external.txt"), "must-not-be-read"); - Files.writeString(root.resolve("pom.xml"), """ - ]> - - - - org.testng - testng - &xxe; - - - - """.formatted(secret.toUri())); - - String output = new Analyzer().analyze(root).render(); - - assertTrue(output.contains("Build system: Maven (could not parse pom.xml)")); - assertFalse(output.contains("must-not-be-read")); } @Test @@ -408,59 +184,6 @@ void missingPomIsNotDetected() throws Exception { var report = new Analyzer().analyze(temp); - assertTrue(report.render().contains("No supported build descriptor found")); + assertTrue(report.render().contains("not detected")); } - - @Test - void countsSeleniumLocatorUsageAndKeepsFullConfidence() { - var report = new Analyzer().analyzeSource(""" - class LocatorPage { - void locate() { - By.id("username"); - By.id("password"); - By.name("email"); - By.className("button"); - By.cssSelector(".submit"); - By.xpath("//button"); - By.linkText("Login"); - By.partialLinkText("Log"); - By.tagName("input"); - org.openqa.selenium.By.xpath("//div"); - } - } - """); - - assertEquals(2L, report.locatorCounts().get("By.id")); - assertEquals(1L, report.locatorCounts().get("By.name")); - assertEquals(1L, report.locatorCounts().get("By.className")); - assertEquals(1L, report.locatorCounts().get("By.cssSelector")); - assertEquals(2L, report.locatorCounts().get("By.xpath")); - assertEquals(1L, report.locatorCounts().get("By.linkText")); - assertEquals(1L, report.locatorCounts().get("By.partialLinkText")); - assertEquals(1L, report.locatorCounts().get("By.tagName")); - - assertTrue(report.findings().isEmpty()); - assertEquals(100, report.estimatedConfidence()); - - String output = report.render(); - assertTrue(output.contains("Locator usage:")); - assertTrue(output.contains("By.id:")); - assertTrue(output.contains("By.xpath:")); - } - - @Test - void ignoresNonSeleniumByLikeCalls() { - var report = new Analyzer().analyzeSource(""" - class LocatorPage { - void locate() { - OtherBy.id("username"); - locator.xpath("//div"); - } - } - """); - - assertTrue(report.locatorCounts().isEmpty()); - assertEquals(100, report.estimatedConfidence()); - } - }