Skip to content

Learn

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.

An explicit position for this verification requestTypeScript
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:

EventWorker AWorker B
StartCaptures a positionCaptures the same position
SendRequests A's codeRequests B's code
First receiptCould read B's messageCould 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

ObservationFirst check
Credential or number unavailableKey scope, runtime secret and assigned number
Send action rejectedApplication response and sender configuration
No receipt before deadlineSender delivery status, destination and environment
Receipt has no detected codeMessage template and extraction result
Code entered but verification failsAccount state, expiry and competing requests
Verification succeeds but assertion failsUI 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.