In this Tutorial
We will do the following:
- Use TestContainers to test a public website,
- Use TestContainers to test a localhost website with a port.
GitHub Repository
If you're just here for the GitHub Repo, here it is: github.com/lukegjpotter/SeleniumDemos.
The key files are the
- build.gradle [link],
- TestContainersTest.java [link], and
- LocalHostServerPortControllerTest.java [link].
Explainer
If you're here for an explainer...
The current build, at publication time, of TestContainers is 1.19.8.
I've been playing around with Docker and Spring Boot, and more importantly Selenium Tests. Selenium has support for RemoteWebBrowser and the TestContainers project has support for spinning up Chrome in a Docker Container.
The benefit here is that it removes the need for installing and maintaining Edge, Firefox and/or "Chrome for Testing" (since July 2023, Chrome 115) installs on the Test Machine. When you install a new version of "Chrome for Testing", on the first launch, you are presented MacOS security and then a Google login page and some more stuff that you don't want.
Running the Browser in a Docker Container removes this slight pain point. And almost makes it plug-and-play.
TestContainers will download a Browser Docker Image that is compatible with your Selenium version.
You will need:
- a fast internet connection, as TestContainers will download about 2GB of Docker Images on first run.
- Docker on your machine, e.g. Docker Desktop on MacOS
If you want to get ahead of the 2GB download of Docker Images, you will need:
The version tags are as of the time of writing, 6th July 2024. But, as stated above, TestContainers will pull the correct versions for you.
lukegjpotter@Lukes-MacBook-Pro ~ % docker pull alpine:3.17 \
&& docker pull selenium/standalone-chrome:4.19.1 \
&& docker pull testcontainers/ryuk:0.7.0 \
&& docker pull testcontainers/vnc-recorder:1.3.0
So you end up with the following
lukegjpotter@Lukes-MacBook-Pro ~ % docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine 3.17 06929782def5 2 weeks ago 7.08MB
selenium/standalone-chrome 4.19.1 cd287a41194d 3 months ago 1.19GB
testcontainers/ryuk 0.7.0 d2ed07d3edac 3 months ago 15.2MB
testcontainers/vnc-recorder 1.3.0 35a6350968be 19 months ago 686MB
So let's get started on the Spring Initializer, start.spring.io. First off, you don't need any of the Dependencies. The actual "testcontainers" Spring Boot dependency mainly has Annotations for Service Connections for Databases, Message Queues and Kafka. Spring Boot Documentation: Testcontainers -> Service Connections. Thusly we don't need it for Selenium.
As we only need the project structure and Metadata, and no dependencies, from the Spring Initializer, we Generate the project and open it in the IDE. We'll add the required Dependencies to the build.gradle:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:selenium'
testImplementation 'org.seleniumhq.selenium:selenium-java:4.22.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
Next we will create the test class: TestContainersTest.java
Our packages are:
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.springframework.boot.test.context.SpringBootTest;
import org.testcontainers.containers.BrowserWebDriverContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.io.File;
import java.time.Duration;
import static org.assertj.core.api.Assertions.assertThat;
The Class Annotations are:
@Testcontainers
@SpringBootTest
public class TestContainersTest {
We declare our Container, the output directory of the failing recordings, and RemoteWebDriver as follows:
private static final File recordingsOutput = new File("./build/test-recordings/");
@Container
private final BrowserWebDriverContainer<?> BrowserWebDriverContainer =
new BrowserWebDriverContainer<>()
.withRecordingMode(VncRecordingMode.SKIP,
recordingsOutput);
private RemoteWebDriver driver;
We're skipping the Recordings, they serve little purpose here. For more complicated tested, you can use VncRecordingMode.RECORD_FAILING.
The SetUp and TearDown methods look like this: if you just want the BeforeAll and AfterAll methods, just declare the Container and RemoteWebDriver as static. We need to actually create the output directory for the VNC Recordings, as it is not created otherwise.
@BeforeAll
static void beforeAll() {
if (!recordingsOutput.exists()) recordingsOutput.mkdirs();
}
@BeforeEach
void setUp() {
driver = new RemoteWebDriver(
BrowserWebDriverContainer.getSeleniumAddress(),
new ChromeOptions());
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));
}
@AfterEach
void cleanUp() {
driver.quit();
}
Finally, our Test is pretty basic with AssertJ (comes as part of the SprintBootTest annotation):
@Test
void phpTravels_PageTitle() {
driver.get("https://phptravels.com/demo/");
String pageTitle = driver.getTitle();
assertThat(pageTitle)
.isEqualTo("Book Your Free Demo Now - Phptravels");
}
Run the Test, and unless you've downloaded the Images in advance, the first run will take a very long time for the Docker Images to be downloaded. You'll also need Docker running on your machine whilst running these tests.
What's Next?
You can also add the LocalServerPort if you need to test on localhost.
But I'll save that for another blog post, as I want that Google Ads Revenue, although given the target audience, you're all using the AdBlockers. :)
Just Joking
We add the Spring Boot Web Dependency to the build.gradle, just add "-web" to the Spring Boot Starter. This will give us Annotations suchs as @RestController and @GetMapping.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
Refresh Gradle, or Maven, if you're old school.
Next add a very rough (no code reviews on this section please) GET method that returns some raw HTML (this Class goes in the src/main/java/... directory):
@RestController
@RequestMapping("/html")
public class LocalHostServerPortController {
@GetMapping("/simplehtml")
public ResponseEntity<String> getSimpleHtml() {
return ResponseEntity.ok("<html><head>"
+ "<title>Local Test Works!!</title>
"
+ "</head><body>Testing on Local</body></html>");
}
}
And we create a new, or update our existing, Selenium Test Class:
@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class LocalHostServerPortControllerTest {
private static final File recordingsOutput = new File("./build/test-recordings/");
@Container
private final BrowserWebDriverContainer BrowserWebDriverContainer<?>
= new BrowserWebDriverContainer<>()
.withRecordingMode(VncRecordingMode.SKIP,
recordingsOutput);
private RemoteWebDriver driver;
@LocalServerPort
private int localServerPort;
@BeforeAll
static void beforeAll() {
if (!recordingsOutput.exists()) recordingsOutput.mkdirs();
}
@BeforeEach
void setUp() {
driver = new RemoteWebDriver(
BrowserWebDriverContainer.getSeleniumAddress(), new ChromeOptions());
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));
}
@AfterEach
void cleanUp() {
driver.quit();
}
@Test
void localhostPortTest() {
driver.get(
"http://host.docker.internal:"
+ localServerPort
+ "/html/simplehtml");
String pageTitle = driver.getTitle();
assertThat(pageTitle).isEqualTo("Local Test Works!!");
}
}
In this updated class, we add the WebEnvironment.RANDOM_PORT, then assign it to a variable via the @LocalServerPort. Finally the Driver URL is host.docker.internal and the LocalServerPort, then append the Endpoint.
Happy Testing,
Luke
No comments:
Post a Comment
Note: only a member of this blog may post a comment.