Create and run resilient UI-focused test prompts with isolated browsers and disposable MailSlurp inboxes
Use an adaptive AI test prompt when the goal is to validate a real user journey through the current UI, but the interface changes too frequently for a selector-heavy automated test to remain useful.
In this method, a test script is a Markdown execution prompt. It tells an AI agent what outcome to prove, which boundaries must remain intact, and what evidence counts as passing. It is not a permanent Playwright script and should not assume that labels, selectors, routes, or intermediate screens remain unchanged.
Run it one step at a time. Write a small script for the current step, run it, and look at the page. Then decide the next step from what the page shows. Do not write one script that runs the whole test. A step that did not do what you expected is a reason to change the next step, not to start over.
Store these prompts under:
tests/manual/<flow-name>.md
Author and run them with the two reusable skills below. Keep each product scenario as a Markdown prompt. Do not add a new skill for every flow.
Type /create-automated-adaptive-ui-test in chat. The skill lives at
.cursor/skills/create-automated-adaptive-ui-test/ and writes a self-contained
prompt under tests/manual/.
Every generated prompt that uses MailSlurp must embed copy-pasteable snippets
in a MailSlurp examples section. Pointing at MAILSLURP.md is not enough.
Match the style in tests/manual/agent-email-conversation.md.
Type /run-automated-adaptive-ui-test in chat and name the file. The skill
lives at .cursor/skills/run-automated-adaptive-ui-test/.
The Markdown file is the test. The agent executes it with browser MCP and the embedded MailSlurp snippets. It must not rewrite the prompt into a permanent executable test program.
When a prompt must prove internal state the UI does not show (for example
account memory or a seeded room), add a
helper endpoint (/create-helper-endpoint).
Those reads need the session cookie and Bearer CRON_SECRET.
If browser MCP needs a signed-in user, follow tests/manual/auth-login.md.
The always-on rule .cursor/rules/browser-mcp-login.mdc points agents at that
file so login details do not have to be repeated in every prompt.
Do not reuse a personal account, a previous test user, or an existing session. Create a fresh MailSlurp inbox and generated password, then sign up, verify, and sign in through the visible auth UI.
When the prompt under test is auth-login.md itself, run that file as written,
including logout and the second sign-in. For any other prompt, reuse only the
signup and first sign-in procedure.
| Prompt | Use |
|---|---|
tests/manual/auth-login.md | Canonical signup, verification, sign-in, logout, and sign-in again |
tests/manual/agent-email-conversation.md | Long two-role email negotiation and dashboard privacy |
tests/manual/brand-login-required-error.md | Focused login-gate bug reproduction |
tests/manual/volt-hydration-maya-ortiz-offer-pay.md | Dual Approve then brand Send Payment (Maya / Volt) |
tests/manual/zly-1932-offer-rewrite-requires-reapprove.md | Rewrite clears brand stamp; Offer-tab Counter is chat to Z |
tests/manual/zly-1933-booked-offer-refuse-new-room.md | Booked offer is frozen; talent can message the brand; only the brand starts a new offer |
tests/manual/zly-1934-quiet-new-offer-or-continuation.md | After silence on an unbooked offer, brand greeting asks new vs continuation; new offer gets its own room |
This approach is primarily for end-to-end UI behavior:
Email can support the test without being its main subject. For example, MailSlurp can supply disposable signup addresses and verification codes while the assertions focus on the browser UI.
Use ordinary unit, integration, and deterministic browser tests for stable business rules. The adaptive prompt complements those tests; it does not replace them.
Use /create-automated-adaptive-ui-test so the required sections, isolation
rules, MailSlurp snippets, and cleanup step are included.
Describe the user journey in plain language. State the final outcome, not the current implementation.
Good:
A brand submits an offer through a talent's public link, both sides can continue the negotiation, and each dashboard shows only the messages that participant is allowed to see.
Fragile:
Click the third button, wait for
.message-row:nth-child(4), then open/dashboard/agent/conversations.
List every checkpoint that proves the journey worked:
Do not let an intermediate success stand in for the final assertion. If the goal is “the other side receives approval,” sending approval is not enough; the test must verify receipt.
Tell the agent to inspect the current accessibility tree, page text, network requests, and application code when needed. Name controls by purpose, such as “the action that opens Agent Links,” rather than freezing an exact selector.
Exact values are appropriate only when they are product contracts:
The prompt should tell the agent to record, for the current run, in a private temporary file outside the repository:
These values must be discovered again on every run. Never copy temporary IDs, verification codes, passwords, cookies, or inbox addresses from an older test into the prompt or the report.
If the test creates an inbox, waits for mail, replies, or deletes an inbox,
include a MailSlurp examples section with the commands the executor should
run. Use placeholders such as /path/to/zooly2 and inboxId.
A prompt that only links to
.cursor/skills/create-automated-adaptive-ui-test/MAILSLURP.md is incomplete.
The prompt should distinguish:
Allow the agent to take a fresh page snapshot, inspect network responses, wait, or retry a visible interaction. Do not authorize bypassing authentication, writing directly to the database, or calling business functions to manufacture a passing state.
Use one isolated browser context per role. Pages in different contexts must not share cookies or browser storage.
For a two-party flow, use names such as:
talent
brand
Start every run from a new named context. If a selected context is already signed in, log out through the visible product UI and confirm it is anonymous before continuing.
Keep both browsers open while the test runs. This makes it possible to verify that updates appear for both roles and that private messages do not cross the role boundary.
If a browser tab is accidentally closed, reopen the exact known application or conversation URL in the same isolated context. Confirm authentication and conversation history before continuing.
Keep the key in the repository root .env.local:
MAILSLURP_API_KEY="..."
Never commit the real key or paste it into documentation, test prompts, source files, or chat output.
Every test talks to MailSlurp through one helper in the repo,
tests/manual/lib/mailslurp.mjs. It uses the MailSlurp package installed at
the repo root, so any checkout that ran npm install has it, and it can be
imported from a script anywhere on disk. It reads the key from the
environment, then from the root .env.local.
There is no fallback to raw MailSlurp web requests. A fallback can turn a test setup mistake into a fake product bug: MailSlurp's plain "send email" request ignores the inbox you name and sends from a brand-new random address, so the agent correctly ignores the reply as coming from a stranger. The helper sends from the inbox itself and stops if the message left from any other address.
Start every run with the self-check. It creates two inboxes, sends one message between them, confirms the sender, and deletes both:
node /path/to/zooly2/tests/manual/lib/mailslurp.mjs check
When something is wrong, the helper stops with the reason: the package is not installed, the key is missing or rejected, nothing arrived before the timeout, or a message went out from the wrong address.
node /path/to/zooly2/tests/manual/lib/mailslurp.mjs create-inbox
Create separate inboxes for separate users. Avoid a short expiry when the test may pause or continue later.
Keep a private cleanup list of only the inbox IDs created by this run. Do not put those IDs in the test report.
Wait for a new email instead of sleeping for a fixed duration:
import * as mailslurp from "/path/to/zooly2/tests/manual/lib/mailslurp.mjs";
const email = await mailslurp.waitForLatestEmail(inboxId, 60_000);
const code = (email.body ?? "").match(/\b(\d{6})\b/)?.[1];
if (!code) throw new Error("Verification code not found");
When an inbox can receive multiple kinds of email, do not assume the newest message is the one under test. Compare the inbox state before and after an action, then identify new messages by timestamp, subject, sender, recipient, and content.
Do not print full bodies if they may contain private data. Extract only the facts needed for the assertion.
Present the test report first. Then delete every inbox this run created, with no question to the user:
node /path/to/zooly2/tests/manual/lib/mailslurp.mjs delete-inbox <inboxId>
Delete only the inboxes this run created, including inboxes from a failed or partial try. If a delete fails, the helper says why. Report that reason and do not claim the cleanup worked. Then remove the private credential file and confirm the count only, without exposing IDs, addresses, codes, or passwords.
Do not call “delete all inboxes.” Other developers or active tests may own them.
A common local topology is:
http://localhost:3003http://localhost:3004http://localhost:3004/dashboardDiscover the current ports rather than assuming they are unchanged.
The authentication identity and Zooly app account are separate concerns. After signup or login through the auth app, visit the local Zooly app so its database can create or resolve the application account.
Before testing:
.env.local points at the intended local dependencies.tests/manual/auth-login.md when the scenario needs a user.Local and dev databases are different. A room created locally cannot be
resolved by an inbound worker that calls dev.zooly.ai, and a dev account does
not automatically exist in the local database.
Outbound email can still be tested locally when configured. A complete inbound
email conversation additionally requires SES routing whose callback targets
the same local environment and database. If a local notification uses
SendGrid with Reply-To: support@zooly.ai, it validates outbound delivery only;
do not send test replies to support.
Use:
https://auth.zooly.aihttps://dev.zooly.aiWait for the intended deployment to finish before starting. Create all test accounts and conversations in dev; do not reuse local room IDs.
Recommended sequence:
auth.zooly.ai.tests/manual/auth-login.md.dev.zooly.ai so the dev app account is provisioned.auth.zooly.ai with a second MailSlurp inbox.dev.zooly.ai so the brand app account exists in the
dev database.For an email-reply flow, verify the first notification before replying:
Reply-To contains the current room ID and dev reply domain.dev.zooly.ai and the current room.Stop if any of these identify another environment.
Do not validate only the side that initiated the action.
For each role:
For privacy-sensitive flows, explicitly list messages that must be absent from the other role's view. A complete transcript in an operator or talent view can be correct while the same transcript in a customer or brand view is a data leak.
The final test report should state:
Never include API keys, passwords, verification codes, auth cookies, inbox IDs, inbox addresses, or full private messages in the report.
.cursor/skills/create-automated-adaptive-ui-test/.cursor/skills/run-automated-adaptive-ui-test/tests/manual/auth-login.md.cursor/rules/browser-mcp-login.mdctests/manual/agent-email-conversation.mdtests/manual/brand-login-required-error.mdCopy the structure of those prompts for new scenarios, but rewrite the product goal, invariants, pass criteria, and privacy assertions for the feature being tested.
On This Page
OverviewSkillsCreate a promptRun a promptInspect readsLoginExisting promptsWhat this method testsWrite the Markdown test prompt1. Start with the product goal2. Define observable pass criteria3. Describe invariants, not selectors4. Make temporary state explicit5. Embed runnable MailSlurp snippets6. Define recovery behaviorBrowser setupMailSlurp lifecycleConfigure the API keyOne MailSlurp client, no fallbackCreate one inbox per roleRead verification or notification emailDelete inboxes when the run endsRun locallyRun on devValidate each role's UIReport the resultReference