TestVibe turns a plain-language description of a user journey into a runnable Playwright test, and the quality of that test tracks the quality of your description almost one-for-one. A vague description gets you a test that clicks around without proving anything; a specific one usually generates clean on the first try. Four habits make the difference: start at the entrypoint, keep one behavior per scenario, name the visible UI, and state the outcome you expect.
Say what proves it worked
The generator reads your intent and produces code that mirrors it. If your description only lists actions, the test only performs actions. The most common reason a generated test is useless is that it never asserts anything, because the description never said what success looks like.
TestVibe's own framing is worth internalizing: good instructions tell it what the user does and what visible result proves the behavior worked. The second half is the one people skip.
# Vague — no outcome
Scenario: Login works
When the user logs in
Then it should work
# Specific — the Then step names observable evidence
Scenario: User signs in with valid credentials
Given the user is on the sign-in page
When the user enters a valid email and password
And the user clicks "Sign in"
Then the dashboard should open
And the user's name should be visible in the header
The rewrite tells the generator what page to start from, which action matters, and two pieces of visible evidence to check. it should work gives it nothing to write a expect() against.
Start at the entrypoint, then navigate forward
Every feature should begin where a real user begins: the page your project's URL opens fresh. That's the home page, or the login page for an app behind authentication. From there, walk to the deeper screen the scenario needs with explicit steps. Don't assume an already-signed-in session or an already-open settings page. The cloud test session starts cold, and a description that skips the path to the screen leaves the generator guessing how to get there.
Put shared setup in a Background so every scenario inherits the same starting walk:
Feature: Team member roles
Background:
Given I am on the "Login" page
When I sign in with "{{var:USERNAME}}" and "{{secret:PASSWORD}}"
And I open the Settings menu
And I click "Team members"
Scenario: Change a member's role
When the user changes a member's role to "Admin"
Then the member's role should show "Admin"
Two rules keep this reliable. Name the entrypoint by its screen state (Given I am on the "Login" page), not by a claim about session state. And never hardcode a URL, domain, or localhost in a step. TestVibe already points the test at your project's configured site, so name the screen, not the address. A hardcoded localhost is especially bad: the cloud session can't reach your machine, and generation will fail trying.
One behavior per scenario
A scenario that tries to cover sign-in, profile update, password reset, and invoice download is four tests wearing a trenchcoat. The generator has to hold all four flows in mind at once, the resulting code is long and brittle, and when it fails you can't tell which behavior broke.
Keep each scenario to one behavior with one clear outcome.
| Focused | Too broad |
|---|---|
| Reset password with a valid email. | Sign in, update profile, reset password, and download an invoice. |
| Add one product to the cart. | Browse, search, compare, check out, and cancel the order. |
If several scenarios share the same setup, that's what Background is for. If one scenario genuinely needs a long lead-in that others don't, keep that setup inside the scenario. When a flow is legitimately large, split it into smaller features rather than growing one scenario until it sprawls.
Focused scenarios also read better in results. A failed run points at a single named behavior instead of a five-step chain where step three of four went wrong.
Name the visible UI, not the implementation
The generator finds elements the way a person would: by the text they can see. So give it that text. Quote the button labels, field names, menu items, and messages exactly as they appear on screen. Skip anything a user can't observe, like CSS classes, element IDs, or API calls. Those are implementation detail, and they change more often than the visible label does.
# Vague — the generator has to guess what to click and what to check
Scenario: Settings
When I change settings
Then it saves
# Specific — every action and outcome names on-screen text
Scenario: User updates notification preference
Given the user is on the account settings page
When the user turns on email notifications
And the user clicks "Save"
Then a "Settings saved" message should appear
And email notifications should still be on after the page reloads
A useful gut check: if a human tester couldn't follow your description without asking questions, the generator can't either. Every "Save", "Team members", and "Settings saved" in the rewrite is an anchor the generated locator can grab.
Here's the same principle applied to a few common weak steps:
| Weak | Better |
|---|---|
When the user logs in | When the user enters a valid email and password and clicks "Sign in" |
Then it should work | Then the dashboard should open and the user's name should be visible |
Check billing | Then the billing page should show the current subscription plan |
Do the setup | Given the user is signed in as an administrator |
Write so the test can run twice
A test that passes once and collides with its own leftover data on the second run isn't finished. This bites hardest on scenarios that create something: a sign-up email, a new project, a named record. A fixed literal like casey@example.com works the first time and fails the next, because the account already exists.
Wrap any value the test creates in the {{unique}} token. It expands to a fresh stamp at run time, and every occurrence within a single run resolves to the same value, so a later step can sign back in with, or assert against, exactly what an earlier step created.
When the user signs up with email "casey+{{unique}}@example.com"
And the user creates a project named "Project {{unique}}"
Only wrap values the test creates. A step that merely references existing data should stay a plain literal. For the rest of repeatability: prefer stable test accounts, describe how the test reaches its starting state, and avoid depending on today's date or on data another user might change out from under you.
The honest part
None of this makes generation magic. What it does is give the AI enough to work with, and TestVibe keeps itself honest after the fact: generated tests are verified by actually running them, and if the output is thin you're meant to read the feature first and improve the description rather than regenerate blindly. A clear entrypoint, one behavior, named UI text, and a real Then are the difference between a first-pass test you can trust and three rounds of regenerating hoping it improves.
Start with your next flaky or half-written test description, rewrite it against these four habits, and generate. If you want the full syntax reference behind these examples, see the Gherkin and feature files guide — or get early access and try a rewrite on your own app.