diff --git a/gradle.properties b/gradle.properties
index c4f21c1..1478313 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -25,6 +25,8 @@ jsoupVersion=1.18.1
htmlunitVersion=4.4.0
flywayVersion=10.17.1
classgraphVersion=4.8.175
+seleniumVersion=4.24.0
+webdrivermanagerVersion=5.9.2
# For deploy to maven central
SONATYPE_HOST=CENTRAL_PORTAL
SONATYPE_CONNECT_TIMEOUT_SECONDS=300
diff --git a/springdog-project/springdog-agent/src/main/resources/templates/content/error-tracing/configuration.html b/springdog-project/springdog-agent/src/main/resources/templates/content/error-tracing/configuration.html
index 72d8e56..e737ad4 100644
--- a/springdog-project/springdog-agent/src/main/resources/templates/content/error-tracing/configuration.html
+++ b/springdog-project/springdog-agent/src/main/resources/templates/content/error-tracing/configuration.html
@@ -18,7 +18,7 @@
+ xmlns:th="http://www.thymeleaf.org">
configuration
@@ -97,7 +97,7 @@ Error Trace
-
+
diff --git a/springdog-project/springdog-tests/springdog-e2e-agent-tests/build.gradle b/springdog-project/springdog-tests/springdog-e2e-agent-tests/build.gradle
index b9b7cf3..b4ce326 100644
--- a/springdog-project/springdog-tests/springdog-e2e-agent-tests/build.gradle
+++ b/springdog-project/springdog-tests/springdog-e2e-agent-tests/build.gradle
@@ -19,8 +19,10 @@ dependencies {
testImplementation "org.springframework.security:spring-security-test:${springVersion}"
testImplementation "org.springframework.boot:spring-boot-starter-test:${springbootVersion}"
- testImplementation group: 'org.htmlunit', name: 'htmlunit', version: '4.4.0'
- testImplementation "org.seleniumhq.selenium:htmlunit3-driver:4.23.0"
+
+ implementation "org.seleniumhq.selenium:selenium-java:${seleniumVersion}"
+ implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}"
+ implementation "io.github.bonigarcia:webdrivermanager:${webdrivermanagerVersion}"
}
test {
diff --git a/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/org/easypeelsecurity/agenttests/AgentTests.java b/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/org/easypeelsecurity/agenttests/AgentTests.java
index 2048bf4..e221f94 100644
--- a/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/org/easypeelsecurity/agenttests/AgentTests.java
+++ b/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/org/easypeelsecurity/agenttests/AgentTests.java
@@ -16,157 +16,136 @@
package org.easypeelsecurity.agenttests;
-
import static org.assertj.core.api.Assertions.assertThat;
+import static org.openqa.selenium.By.cssSelector;
+import static org.openqa.selenium.By.xpath;
+
+import java.util.List;
-import java.io.IOException;
-
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.web.server.LocalServerPort;
-
-import org.htmlunit.WebClient;
-import org.htmlunit.html.HtmlButton;
-import org.htmlunit.html.HtmlForm;
-import org.htmlunit.html.HtmlPage;
-import org.htmlunit.html.HtmlPasswordInput;
-import org.htmlunit.html.HtmlTable;
-import org.htmlunit.html.HtmlTableRow;
-import org.htmlunit.html.HtmlTextInput;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.RetryingTest;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.ExpectedConditions;
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-class AgentTests {
- final String AGENT_USERNAME = "username";
- final String AGENT_PASSWORD = "password";
-
- @LocalServerPort
- private int port;
-
- private WebClient webClient;
-
- @BeforeEach
- public void setUp() {
- webClient = new WebClient();
- webClient.getOptions().setThrowExceptionOnScriptError(false);
- webClient.getOptions().setCssEnabled(false);
- webClient.getOptions().setJavaScriptEnabled(false);
- }
+import support.SeleniumTestSupport;
- @AfterEach
- public void tearDown() {
- if (webClient != null) {
- webClient.close();
- }
- }
+class AgentTests extends SeleniumTestSupport {
@Test
@DisplayName("Redirect to the login page when accessing the home PATH")
- void should_redirect_to_loginPage() throws IOException {
- HtmlPage page = webClient.getPage("http://localhost:" + port + "/springdog/");
- assertThat(page.getUrl()).hasToString(getAgentPath("/login"));
+ void should_redirect_to_loginPage() {
+ accessPage("/");
+ assertThat(currentUrl()).isEqualTo(getAgentPath("/login"));
}
-
@Test
@DisplayName("Redirect to home page after successful login")
- void login_success() throws IOException {
- AgentTestHelper agent = new AgentTestHelper();
- HtmlPage resultPage = agent.login(AGENT_USERNAME, AGENT_PASSWORD);
-
- assertThat(resultPage.getUrl()).hasToString(getAgentPath(""));
- assertThat(resultPage.asXml()).contains("Dashboard", "Total Endpoints");
+ void login_success() throws InterruptedException {
+ withLogin();
+ assertThat(currentUrl()).isEqualTo(getAgentPath(""));
+ assertThat(pageSource()).contains("Dashboard", "Total Endpoints");
}
@Test
@DisplayName("Display error message when login fails")
- void login_fail() throws IOException {
- AgentTestHelper agent = new AgentTestHelper();
- HtmlPage resultPage = agent.login("wrong_username", "wrong_password");
+ void login_fail() throws InterruptedException {
+ withWrongLogin();
- assertThat(resultPage.getUrl()).hasToString(getAgentPath("/login?error"));
- assertThat(resultPage.asXml()).contains("Login Failed.");
+ assertThat(currentUrl()).isEqualTo(getAgentPath("/login?error"));
+ assertThat(pageSource()).contains("Login Failed.");
}
@Test
- void homePage() throws IOException {
- AgentTestHelper agent = new AgentTestHelper();
- agent.withLogin();
+ void homePage() throws InterruptedException {
+ withLogin();
- HtmlPage page = webClient.getPage(getAgentPath("/"));
- assertThat(page.asXml()).contains("Dashboard", "Total Endpoints");
+ accessPage("/");
+ assertThat(pageSource()).contains("Dashboard", "Total Endpoints");
}
@Test
- void ratelimit_detail() throws IOException {
- AgentTestHelper agent = new AgentTestHelper();
- agent.withLogin();
-
- HtmlPage page = webClient.getPage(getAgentPath("/rate-limit"));
- HtmlTable table = page.getFirstByXPath("//table[@id='table']");
- HtmlTableRow row = table.getRow(1); // Skip the header row
- HtmlButton analyzeBtn = row.getCell(4).getFirstByXPath(".//button[@title='Analyze']");
- HtmlPage detailPage = analyzeBtn.click();
-
- // then
- assertThat(detailPage.asNormalizedText()).contains("""
- Path Method Signature HTTP Method Status Actions
- /api/hello java.lang.String org.easypeelsecurity.agenttests.ExampleController.hello() GET NOT_CONFIGURED""");
+ @RetryingTest(5)
+ void ratelimit_detail() throws InterruptedException {
+ withLogin();
+
+ accessPage("/rate-limit");
+ WebElement analyzeBtn = accessPageUntilXPath("/rate-limit", "(//button[@title='Analyze'])[1]");
+ analyzeBtn.click();
+
+ String pageText = getDriver().findElement(By.tagName("body")).getText();
+ assertThat(pageText).contains(
+ "Endpoint Information\nPath: /api/hello\nEndpoint Method Signature: java.lang.String org.easypeelsecurity.agenttests.ExampleController.hello()\nHTTP Method: GET\nPattern Path: false\nRule Status: NOT_CONFIGURED\nIP Based Rule: false\nPermanent Ban Rule: false\nRequest Limit Count: 0\nTime Limit (seconds): 0\nBan Time (seconds): 0\n");
}
@Test
- void ratelimit_read() throws IOException {
- AgentTestHelper agent = new AgentTestHelper();
- agent.withLogin();
+ @RetryingTest(5)
+ void ratelimit_read() throws InterruptedException {
+ withLogin();
- HtmlPage page = webClient.getPage(getAgentPath("/rate-limit"));
- HtmlTable table = page.getFirstByXPath("//table[@id='table']");
- HtmlTableRow row = table.getRow(1); // Skip the header row
+ WebElement table =
+ accessPageUntil("/rate-limit", ExpectedConditions.presenceOfElementLocated(By.id("table")));
+ WebElement row = table.findElements(By.tagName("tr")).get(1);
- // check row
- assertThat(row.getCell(0).getTextContent()).isEqualTo("/api/hello");
- assertThat(row.getCell(1).getTextContent()).isEqualTo(
+ List cells = row.findElements(By.tagName("td"));
+ assertThat(cells.get(0).getText()).isEqualTo("/api/hello");
+ assertThat(cells.get(1).getText()).isEqualTo(
"java.lang.String org.easypeelsecurity.agenttests.ExampleController.hello()");
- assertThat(row.getCell(2).getTextContent()).isEqualTo("GET");
- assertThat(row.getCell(3).getTextContent()).contains("NOT_CONFIGURED");
- HtmlButton configureButton = row.getCell(4).getFirstByXPath(".//button[@title='Configure']");
- assertThat(configureButton).isNotNull();
- HtmlButton analyzeButton = row.getCell(4).getFirstByXPath(".//button[@title='Analyze']");
- assertThat(analyzeButton).isNotNull();
+ assertThat(cells.get(2).getText()).isEqualTo("GET");
+ assertThat(cells.get(3).getText()).contains("NOT_CONFIGURED");
+ assertThat(cells.get(4).findElement(xpath(".//button[@title='Configure']"))).isNotNull();
+ assertThat(cells.get(4).findElement(xpath(".//button[@title='Analyze']"))).isNotNull();
}
- class AgentTestHelper {
- HtmlPage login(String username, String password) throws IOException {
- HtmlPage loginPage = webClient.getPage(getAgentPath("login"));
-
- // Get the form
- HtmlForm loginForm = loginPage.getForms().get(0);
- assertThat(loginForm).isNotNull();
-
- // Fill in the form
- HtmlTextInput usernameInput = loginForm.getInputByName("username");
- HtmlPasswordInput passwordInput = loginForm.getInputByName("password");
- HtmlButton submitButton = loginForm.getFirstByXPath("//button[@type='submit']");
- usernameInput.type(username);
- passwordInput.type(password);
-
- // Submit
- return submitButton.click();
- }
-
- void withLogin() throws IOException {
- login(AGENT_USERNAME, AGENT_PASSWORD);
- }
+ @Test
+ @RetryingTest(5)
+ void errorTraceConfigurationSearchTest() throws InterruptedException {
+ withLogin();
+
+ WebElement searchInput = accessPageUntil("/error-tracing/configuration",
+ ExpectedConditions.visibilityOfElementLocated(By.id("searchInput")));
+ assertThat(searchInput).isNotNull();
+
+ performSearch("java.io.ObjectStrea", "java.io.ObjectStreamException",
+ "java.lang.MatchException");
+ performSearch("MatchException", "java.lang.MatchException", "java.io.ObjectStreamException");
+ performSearch("nullpointer", "java.lang.NullPointerException",
+ "java.io.ObjectStreamException");
}
+ private void performSearch(String searchTerm, String expectedResult, String notExpectedResult) {
+ WebElement searchInput = getDriver().findElement(By.id("searchInput"));
+ searchInput.clear();
+ searchInput.sendKeys(searchTerm);
+
+ getWait().until(ExpectedConditions.attributeToBe(searchInput, "value", searchTerm));
+ List visibleItems = getDriver().findElements(
+ cssSelector("li.list-group-item.exception-item:not([style*='display: none']) div label"))
+ .stream()
+ .map(element -> element.getAttribute("innerHTML"))
+ .toList();
+
+ assertThat(visibleItems).isNotEmpty();
+ assertThat(visibleItems).contains(expectedResult);
+ assertThat(visibleItems).doesNotContain(notExpectedResult);
+ }
- private String getAgentPath(String path) {
- if (path.startsWith("/")) {
- path = path.substring(1);
- }
- return "http://localhost:" + port + "/springdog/" + path;
+ @Test
+ @RetryingTest(5)
+ void errorTraceConfigurationEnableTest() throws InterruptedException {
+ withLogin();
+
+ WebElement accordionButton =
+ accessPageUntilCssSelector("/error-tracing/configuration", "h2[id^='heading'] button[type='button']");
+ accordionButton.click();
+
+ getWait().until(
+ ExpectedConditions.visibilityOfElementLocated(cssSelector("li.list-group-item.exception-item")));
+ WebElement exceptionClasses =
+ getDriver().findElements(cssSelector("li.list-group-item.exception-item")).getFirst();
+ WebElement checkbox = exceptionClasses.findElement(xpath(".//input[@type='checkbox']"));
+ checkbox.click();
+ assertThat(checkbox.isSelected()).isFalse();
}
}
-
diff --git a/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/org/easypeelsecurity/agenttests/ExampleControllerTest.java b/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/org/easypeelsecurity/agenttests/ExampleControllerTest.java
index df73c9d..06fdb04 100644
--- a/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/org/easypeelsecurity/agenttests/ExampleControllerTest.java
+++ b/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/org/easypeelsecurity/agenttests/ExampleControllerTest.java
@@ -18,35 +18,18 @@
import static org.assertj.core.api.Assertions.assertThat;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.web.server.LocalServerPort;
-
-import org.htmlunit.WebClient;
-import org.htmlunit.html.HtmlPage;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-class ExampleControllerTest {
-
- @LocalServerPort
- private int port;
+import support.SeleniumTestSupport;
- private WebClient webClient;
-
- @BeforeEach
- public void setUp() {
- webClient = new WebClient();
- webClient.getOptions().setThrowExceptionOnScriptError(false);
- webClient.getOptions().setCssEnabled(false);
- webClient.getOptions().setJavaScriptEnabled(false);
- }
+class ExampleControllerTest extends SeleniumTestSupport {
@Test
@DisplayName("Should return hello world")
- void shouldReturnHelloWorld() throws Exception {
- HtmlPage page = webClient.getPage("http://localhost:" + port + "/api/hello");
- assertThat(page.getBody().asNormalizedText()).isEqualTo("Hello, World!");
+ void shouldReturnHelloWorld() {
+ accessNotSpringdogPage("/api/hello");
+
+ assertThat(pageSource()).contains("Hello, World!");
}
}
diff --git a/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/support/SeleniumTestSupport.java b/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/support/SeleniumTestSupport.java
new file mode 100644
index 0000000..a9296d3
--- /dev/null
+++ b/springdog-project/springdog-tests/springdog-e2e-agent-tests/src/test/java/support/SeleniumTestSupport.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package support;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.openqa.selenium.By.name;
+import static org.openqa.selenium.By.xpath;
+
+import java.time.Duration;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.openqa.selenium.By;
+import org.openqa.selenium.By.ByCssSelector;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import io.github.bonigarcia.wdm.WebDriverManager;
+
+/**
+ * SeleniumTestSupport.
+ * For functionality not provided here, access it directly via getWait and getDriver.
+ */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public abstract class SeleniumTestSupport {
+ final String AGENT_USERNAME = "username";
+ final String AGENT_PASSWORD = "password";
+
+ @LocalServerPort
+ private int port;
+
+ private WebDriver driver;
+ private WebDriverWait wait;
+
+ @BeforeEach
+ public void setUp() {
+ ChromeOptions options = new ChromeOptions();
+ options.addArguments("--headless");
+ options.addArguments("--disable-gpu");
+ options.addArguments("--no-sandbox");
+ options.setExperimentalOption("excludeSwitches", new String[] {"enable-automation"});
+ this.driver = WebDriverManager.chromedriver().capabilities(options).create();
+ this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+ }
+
+ @AfterEach
+ public void tearDown() {
+ this.driver.quit();
+ }
+
+ public void accessPage(String path) {
+ this.driver.get(getAgentPath(path));
+ }
+
+ public WebElement accessPageUntil(String path, ExpectedCondition condition) {
+ this.driver.get(getAgentPath(path));
+ return this.wait.until(condition);
+ }
+
+ public WebElement accessPageUntilCssSelector(String path, String cssSelector) {
+ this.driver.get(getAgentPath(path));
+ return this.wait.until(ExpectedConditions.presenceOfElementLocated(ByCssSelector.cssSelector(cssSelector)));
+ }
+
+ public WebElement accessPageUntilXPath(String path, String xpathElement) {
+ this.driver.get(getAgentPath(path));
+ return this.wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(xpathElement)));
+ }
+
+ public void accessNotSpringdogPage(String path) {
+ assertThat(path).startsWith("/");
+ this.driver.get("http://localhost:" + port + path);
+ }
+
+ public void withLogin() throws InterruptedException {
+ this.login(AGENT_USERNAME, AGENT_PASSWORD);
+ }
+
+ public void withWrongLogin() throws InterruptedException {
+ this.login("wrong_username", "wrong_password");
+ }
+
+ public String pageSource() {
+ return this.driver.getPageSource();
+ }
+
+ public String currentUrl() {
+ return this.driver.getCurrentUrl();
+ }
+
+ private void login(String username, String password) throws InterruptedException {
+ this.driver.get(getAgentPath("login"));
+
+ WebElement loginForm = this.wait.until(ExpectedConditions.presenceOfElementLocated(By.tagName("form")));
+ WebElement usernameInput = loginForm.findElement(name("username"));
+ WebElement passwordInput = loginForm.findElement(name("password"));
+ WebElement submitButton = loginForm.findElement(xpath("//button[@type='submit']"));
+
+ usernameInput.sendKeys(username);
+ passwordInput.sendKeys(password);
+ submitButton.click();
+
+ if (username.equals(AGENT_USERNAME) && password.equals(AGENT_PASSWORD)) {
+ this.wait.until(
+ ExpectedConditions.visibilityOfElementLocated(xpath("//h1[normalize-space()='Dashboard']")));
+ }
+ }
+
+ public String getAgentPath(String path) {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ return "http://localhost:" + port + "/springdog/" + path;
+ }
+
+ public WebDriver getDriver() {
+ return this.driver;
+ }
+
+ public WebDriverWait getWait() {
+ return this.wait;
+ }
+}