Make SMS acceptance tests repeatable
Most confusing SMS test failures start with unclear number ownership, old messages, or a timeout that hides the failing stage. Make those choices explicit before you add retries.
Put the inbox position before the send
A latest-message read can return yesterday's code. Capture the position immediately before this test requests its SMS, then wait after that position.
const number = phonekit.number(testPhoneNumber)
const after = await number.mark()
await page.getByRole("button", { name: "Send code" }).click()
const receipt = await number.waitForMessage({ after, timeoutMs: 120_000 })
if (!receipt.message.otp) throw new Error("Message arrived without a code")
await page.getByLabel("Verification code").fill(receipt.message.otp)
await page.getByRole("button", { name: "Verify" }).click()
await expect(page.getByRole("heading", { name: "Your account" })).toBeVisible()This is the receiving sequence inside a configured test; the complete recipe supplies imports, credentials and setup. If your task inspects and skips an unrelated receipt, continue from its returned cursor instead of reading it again.
Give independent work independent destinations
Consider two workers sharing a number:
| Event | Worker A | Worker B |
|---|---|---|
| Start | Captures a position | Captures the same position |
| Send | Requests A's code | Requests B's code |
| First receipt | Could read B's message | Could read B's message |
Scroll horizontally to see all columns.
Freshness alone cannot identify which request owns a message. Allocate one number per independently running verification flow, or serialize jobs sharing an inbox. Include overlapping branches and reruns when deciding concurrency.
Account state also needs ownership. A signup test cannot create the same already-registered identity forever. Use a reset fixture, a supported test-account cleanup, or an existing-account sign-in flow according to what the test is meant to cover.
Budget the whole test
Set a receive deadline inside the test runner's overall timeout, leaving time for the initial page actions and final assertion. The SDK's wait deadline includes polling and retry delays. A receiving timeout should finish with a useful failure rather than an unbounded background wait.
A resend is another application action. Decide whether the test covers resend behavior before adding one. An automatic resend can produce competing codes and consume the sending service's rate limit.
Report the stage, then investigate it
| Observation | First check |
|---|---|
| Credential or number unavailable | Key scope, runtime secret and assigned number |
| Send action rejected | Application response and sender configuration |
| No receipt before deadline | Sender delivery status, destination and environment |
| Receipt has no detected code | Message template and extraction result |
| Code entered but verification fails | Account state, expiry and competing requests |
| Verification succeeds but assertion fails | UI readiness and expected final state |
Scroll horizontally to see all columns.
Keep ordinary run logs to stage, timing, request identifiers and outcome. An authorized developer can inspect the inbox when body-level diagnosis is needed. That keeps the failure useful without scattering short-lived verification data across logs.
Questions you may have
Should we retry the whole test?
First identify the stage and make number/account ownership deterministic. A whole-test retry can create a second send while the first is still arriving. If retries remain useful, reset the application state and capture a new position for each attempt.
How do we filter for the right sender?
Use waitForMessage to inspect a fresh receipt and decide whether it belongs to the intended task. Continue from receipt.cursor if you deliberately skip it. The current waiting API does not correlate SMS to a test request; independent numbers are the simplest isolation for concurrent work.
How long should the deadline be?
Choose it from observed delivery behavior and the value of the test, then leave room for browser setup and the final assertion. Phonekit does not promise a universal SMS delivery time. Report a timeout as a receiving-stage failure rather than assuming the application rejected a code.