diff --git a/core/spring-boot-test/build.gradle b/core/spring-boot-test/build.gradle index b353c3518f3..3ef5e388292 100644 --- a/core/spring-boot-test/build.gradle +++ b/core/spring-boot-test/build.gradle @@ -38,6 +38,10 @@ dependencies { optional("org.hamcrest:hamcrest-library") optional("org.junit.jupiter:junit-jupiter-api") optional("org.mockito:mockito-core") + optional("org.seleniumhq.selenium:htmlunit3-driver") { + exclude(group: "com.sun.activation", module: "jakarta.activation") + } + optional("org.seleniumhq.selenium:selenium-api") optional("org.skyscreamer:jsonassert") optional("org.springframework:spring-web") diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClient.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClient.java new file mode 100644 index 00000000000..d7839a1c6b7 --- /dev/null +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClient.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-present 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 + * + * https://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 org.springframework.boot.test.web.htmlunit; + +import java.io.IOException; + +import org.htmlunit.FailingHttpStatusCodeException; +import org.htmlunit.Page; +import org.htmlunit.WebClient; +import org.jspecify.annotations.Nullable; + +import org.springframework.boot.test.http.server.BaseUrl; +import org.springframework.boot.test.http.server.BaseUrlProvider; + +/** + * HTML Unit {@link WebClient} that will automatically prefix relative URLs with a + * {@link BaseUrlProvider provided} {@link BaseUrl}. + * + * @author Phillip Webb + * @since 4.0.0 + */ +public class BaseUrlWebClient extends WebClient { + + private @Nullable BaseUrl baseUrl; + + public BaseUrlWebClient(@Nullable BaseUrl baseUrl) { + this.baseUrl = baseUrl; + } + + @Override + public
P getPage(String url) throws IOException, FailingHttpStatusCodeException {
+ if (this.baseUrl != null && url.startsWith("/")) {
+ url = this.baseUrl.resolve(url);
+ }
+ return super.getPage(url);
+ }
+
+}
diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriver.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriver.java
new file mode 100644
index 00000000000..32aaefd218e
--- /dev/null
+++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriver.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012-present 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
+ *
+ * https://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 org.springframework.boot.test.web.htmlunit;
+
+import org.htmlunit.BrowserVersion;
+import org.jspecify.annotations.Nullable;
+import org.openqa.selenium.Capabilities;
+
+import org.springframework.boot.test.http.server.BaseUrl;
+import org.springframework.boot.test.http.server.BaseUrlProvider;
+import org.springframework.test.web.servlet.htmlunit.webdriver.WebConnectionHtmlUnitDriver;
+
+/**
+ * HTML Unit {@link WebConnectionHtmlUnitDriver} that will automatically prefix relative
+ * URLs with a {@link BaseUrlProvider provided} {@link BaseUrl}.
+ *
+ * @author Phillip Webb
+ * @since 4.0.0
+ */
+public class BaseUrlWebConnectionHtmlUnitDriver extends WebConnectionHtmlUnitDriver {
+
+ private @Nullable BaseUrl baseUrl;
+
+ public BaseUrlWebConnectionHtmlUnitDriver(@Nullable BaseUrl baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ public BaseUrlWebConnectionHtmlUnitDriver(@Nullable BaseUrl baseUrl, boolean enableJavascript) {
+ super(enableJavascript);
+ this.baseUrl = baseUrl;
+ }
+
+ public BaseUrlWebConnectionHtmlUnitDriver(@Nullable BaseUrl baseUrl, BrowserVersion browserVersion) {
+ super(browserVersion);
+ this.baseUrl = baseUrl;
+ }
+
+ public BaseUrlWebConnectionHtmlUnitDriver(@Nullable BaseUrl baseUrl, Capabilities capabilities) {
+ super(capabilities);
+ this.baseUrl = baseUrl;
+ }
+
+ @Override
+ public void get(String url) {
+ if (this.baseUrl != null && url.startsWith("/")) {
+ url = this.baseUrl.resolve(url);
+ }
+ super.get(url);
+ }
+
+}
diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/package-info.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/package-info.java
new file mode 100644
index 00000000000..53ba5769587
--- /dev/null
+++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-present 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
+ *
+ * https://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.
+ */
+
+/**
+ * HtmlUnit support classes.
+ */
+@NullMarked
+package org.springframework.boot.test.web.htmlunit;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClientTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClientTests.java
new file mode 100644
index 00000000000..375d4bb995d
--- /dev/null
+++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClientTests.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012-present 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
+ *
+ * https://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 org.springframework.boot.test.web.htmlunit;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.htmlunit.StringWebResponse;
+import org.htmlunit.WebClient;
+import org.htmlunit.WebConnection;
+import org.htmlunit.WebResponse;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.test.http.server.BaseUrl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.assertArg;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link BaseUrlWebClient}.
+ *
+ * @author Phillip Webb
+ */
+@SuppressWarnings("resource")
+class BaseUrlWebClientTests {
+
+ @Test
+ void createWhenBaseUrlIsNull() throws Exception {
+ BaseUrlWebClient client = new BaseUrlWebClient(null);
+ WebConnection connection = mockConnection();
+ client.setWebConnection(connection);
+ assertThatExceptionOfType(MalformedURLException.class).isThrownBy(() -> client.getPage("/test"));
+ }
+
+ @Test
+ void getPageWhenUrlIsRelativeUsesBaseUrl() throws Exception {
+ WebClient client = new BaseUrlWebClient(BaseUrl.of("https://example.com:8080"));
+ WebConnection connection = mockConnection();
+ client.setWebConnection(connection);
+ client.getPage("/test");
+ thenConnectionRequests(connection, new URL("https://example.com:8080/test"));
+ }
+
+ @Test
+ void getPageWhenUrlIsNotRelativeUsesUrl() throws Exception {
+ WebClient client = new BaseUrlWebClient(BaseUrl.of("https://example.com:8080"));
+ WebConnection connection = mockConnection();
+ client.setWebConnection(connection);
+ client.getPage("https://example.com:9000/test");
+ thenConnectionRequests(connection, new URL("https://example.com:9000/test"));
+ }
+
+ private void thenConnectionRequests(WebConnection connection, URL url) throws IOException {
+ then(connection).should().getResponse(assertArg((request) -> assertThat(request.getUrl()).isEqualTo(url)));
+ }
+
+ private WebConnection mockConnection() throws IOException {
+ WebConnection connection = mock(WebConnection.class);
+ WebResponse response = new StringWebResponse("test", new URL("http://localhost"));
+ given(connection.getResponse(any())).willReturn(response);
+ return connection;
+ }
+
+}
diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriverTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriverTests.java
new file mode 100644
index 00000000000..91b2a32e01b
--- /dev/null
+++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriverTests.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012-present 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
+ *
+ * https://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 org.springframework.boot.test.web.htmlunit;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.htmlunit.TopLevelWindow;
+import org.htmlunit.WebClient;
+import org.htmlunit.WebClientOptions;
+import org.htmlunit.WebConsole;
+import org.htmlunit.WebRequest;
+import org.htmlunit.WebWindow;
+import org.jspecify.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatcher;
+import org.openqa.selenium.WebDriverException;
+
+import org.springframework.boot.test.http.server.BaseUrl;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link BaseUrlWebConnectionHtmlUnitDriver}.
+ *
+ * @author Phillip Webb
+ */
+class BaseUrlWebConnectionHtmlUnitDriverTests {
+
+ private final WebClient webClient;
+
+ BaseUrlWebConnectionHtmlUnitDriverTests() {
+ this.webClient = mock();
+ given(this.webClient.getOptions()).willReturn(new WebClientOptions());
+ given(this.webClient.getWebConsole()).willReturn(new WebConsole());
+ WebWindow currentWindow = mock(WebWindow.class);
+ given(currentWindow.isClosed()).willReturn(false);
+ given(this.webClient.getCurrentWindow()).willReturn(currentWindow);
+ }
+
+ @Test
+ void createWhenBaseUrlIsNull() {
+ BaseUrlWebConnectionHtmlUnitDriver driver = new TestBaseUrlWebConnectionHtmlUnitDriver(null);
+ assertThatExceptionOfType(WebDriverException.class).isThrownBy(() -> driver.get("/test"))
+ .withCauseInstanceOf(MalformedURLException.class);
+ }
+
+ @Test
+ void getWhenUrlIsRelativeUsesBaseUrl() throws Exception {
+ BaseUrl baseUrl = BaseUrl.of("https://example.com");
+ BaseUrlWebConnectionHtmlUnitDriver driver = new TestBaseUrlWebConnectionHtmlUnitDriver(baseUrl);
+ driver.get("/test");
+ then(this.webClient).should()
+ .getPage(any(TopLevelWindow.class), requestToUrl(new URL("https://example.com/test")));
+ }
+
+ private WebRequest requestToUrl(URL url) {
+ return argThat(new WebRequestUrlArgumentMatcher(url));
+ }
+
+ public class TestBaseUrlWebConnectionHtmlUnitDriver extends BaseUrlWebConnectionHtmlUnitDriver {
+
+ TestBaseUrlWebConnectionHtmlUnitDriver(@Nullable BaseUrl baseUrl) {
+ super(baseUrl);
+ }
+
+ @Override
+ public WebClient getWebClient() {
+ return BaseUrlWebConnectionHtmlUnitDriverTests.this.webClient;
+ }
+
+ }
+
+ private static final class WebRequestUrlArgumentMatcher implements ArgumentMatcher