In this tutorial, you’ll generate a clean, production-ready web UI automation framework in under 3 minutes using Cursor—and you’ll validate a real-world scenario: a failed login on SauceDemo for a locked-out user.
I’ll also embed my screen recording in the post so you can follow the exact flow I used end-to-end.
What you’ll build
A working Gradle project using:
- Java 17
- JUnit 5
- Selenide
- Gradle (Groovy DSL)
With best-practice structure:
- Page Object Model (POM)
- Clear separation:
- test logic
- page interactions
- configuration
- External configuration:
- base URL
- credentials
- A real test:
- open
https://www.saucedemo.com - login as
locked_out_user - Verify the error message:
Epic sadface: Sorry, this user has been locked out.
Step 1: Create an empty folder + open it in Cursor
Create a new folder, e.g.
saucedemo-selenide-junit5Open it in Cursor
Make sure Cursor can create files in the project (normal default)
That’s all—no manual Gradle init needed if you’re letting Cursor generate everything.
Step 2: Paste this prompt into Cursor
This is the exact prompt the framework is based on (copy/paste as-is):
Why this prompt works
It forces Cursor to:
- generate all files, not fragments
- follow a known structure
- implement a real assertion
- externalize config (so you don’t hardcode URLs and creds into tests)
Step 3: Make sure Cursor generates these files
After Cursor responds, your repository should include at least:
- build.gradle
- src/test/java/base/BaseTest.java
- src/test/java/config/TestConfig.java
- src/test/java/pages/LoginPage.java
- src/test/java/tests/LoginTest.java
- src/test/resources/application.properties
If you’re missing any of these, ask Cursor:
“Create the missing files and ensure the project runs with
./gradlew test.”
Step 4: What the code should do (high-level)
TestConfig.java
Central place to configure Selenide:
- base URL
- browser (optional)
- timeout
- screenshot / reports folder (optional)
BaseTest.java
JUnit 5 lifecycle hooks:
- set up config once (or before each test)
- optionally clean browser state
LoginPage.java (Page Object)
Encapsulates:
- locators (username, password, login button, error message)
- actions (open page, login)
- assertions (error visible + exact text)
LoginTest.java
Reads like a scenario:
- open login page
- attempt login as locked out user
- assert correct error is shown
Step 5: Run it
From the project root:
You should see:
- Gradle downloads dependencies
- Selenide launches a browser
- test runs
- test passes ✅ (because the expected error is displayed)
Expected behavior on SauceDemo
The locked_out_user is a known user on SauceDemo that always fails login with the message:
Epic sadface: Sorry, this user has been locked out.
Your test should assert the exact message, not something vague—this is important for stable UI test feedback.
Final Thoughts
What you’ve seen here isn’t magic — it’s leverage.
Cursor didn’t “replace” test automation skills. It amplified them.
The reason this worked in under 3 minutes is because the intent, structure, and expectations were clear from the start.
The real takeaway is this:
- If you know what good architecture looks like
- If you can describe clean separation of concerns
- If you understand how tests should read and behave
…then tools like Cursor become a serious productivity multiplier rather than a code generator you have to babysit.
This small example already gives you:
- a maintainable project structure
- a real negative test with a meaningful assertion
- a foundation you can safely extend in a real project
From here, scaling is easy:
- add more page objects
- add reporting
- add CI
- add parallelism
The hardest part — getting started correctly — is already done.
If this tutorial helped you, try repeating the exercise with:
- a positive login flow
- a different browser
- a new assertion
- or an entirely different application
You’ll quickly notice that the speed gain compounds.
The future of test automation isn’t writing less code — it’s spending less time on the wrong code.



