Spring Boot teams often have stable controller tests and flaky end-to-end tests. Playwright can improve browser reliability, but the biggest production failures usually happen in the email step after form submission.
This guide shows a practical setup for Java/Kotlin teams that need both browser confidence and message verification.
Architecture at a glance
| Layer | Tooling | Purpose |
|---|---|---|
| App runtime | Spring Boot test context | Start real app flows on a random local port |
| Browser automation | Playwright | Drive forms, navigation, and UI assertions |
| Message verification | MailSlurp inbox API | Wait for real email and assert token/link behavior |
| Release signal | CI pipeline | Fail builds when user-critical messaging paths break |
1) Start Spring Boot on a random test port
Use RANDOM_PORT so each test process can run in parallel without fixed-port collisions.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SignupFlowTest {
@LocalServerPort
var port: Int = 0
}
This gives you a deterministic target URL per run: http://localhost:$port.
2) Add Playwright to your test dependencies
Gradle
dependencies {
testImplementation("com.microsoft.playwright:playwright:1.52.0")
}
Maven
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.52.0</version>
<scope>test</scope>
</dependency>
3) Build a minimal reusable Playwright harness
companion object {
private lateinit var playwright: Playwright
private lateinit var browser: Browser
@JvmStatic
@BeforeAll
fun setupClass() {
playwright = Playwright.create()
browser = playwright.chromium().launch()
}
@JvmStatic
@AfterAll
fun teardownClass() {
browser.close()
playwright.close()
}
}
private lateinit var context: BrowserContext
private lateinit var page: Page
@BeforeEach
fun setupTest() {
context = browser.newContext()
page = context.newPage()
}
@AfterEach
fun teardownTest() {
context.close()
}
4) Add email verification to the browser journey
Browser-only assertions miss common production failures:
- email never arrives,
- verification link has wrong tenant/environment host,
- token is malformed or expired.
Pattern:
- Create a fresh inbox for this test.
- Submit signup form in Playwright with that address.
- Wait for verification email via API.
- Extract link or code.
- Continue browser flow and assert activated state.
val inbox = mailslurp.inboxController.createInbox()
page.navigate("http://localhost:$port/signup")
page.fill("#email", inbox.emailAddress)
page.click("button[type='submit']")
val email = mailslurp.waitController.waitForLatestEmail(
WaitForLatestEmailOptions().inboxId(inbox.id).timeout(60_000L)
)
val links = mailslurp.emailController.getEmailLinks(email.id)
val verifyLink = links.links.first()
page.navigate(verifyLink)
assertThat(page.textContent("h1")).contains("Account verified")
5) Stabilize CI before scaling suite count
Use these controls before adding more scenarios:
- one inbox per test (or per worker),
- explicit API wait timeouts by flow type,
- trace/screenshot on failure,
- artifact capture for inbox ID + message ID + extracted token/link.
This keeps incident triage fast when failures happen.
Playwright vs MockMvc vs contract tests
MockMvc: fastest for server-side behavior and validation logic.Playwright: best for user-journey/browser-state confidence.- MailSlurp API assertions: best for message-delivery correctness and verification tokens.
Use all three where risk is highest.
Related routes
- Playwright browser automation guide
- Email integration testing
- How to test emails in development
- Email for testing operating model
- Email Sandbox
Final take
For Spring Boot teams, Playwright is strongest when combined with real message verification. Treat email as part of the transaction boundary, not a side effect, and your release confidence improves materially.