En esta página
Por qué el OTP rompe la automatización
Una prueba puede hacer clic, escribir y comprobar, pero no puede leer un teléfono. Cada uno de los apaños habituales sacrifica algo:
- Desactivar el 2FA en staging significa que nunca se prueba el inicio de sesión que de verdad usan los usuarios.
- Los códigos de prueba fijos solo funcionan en sistemas que controlas, y ocultan los problemas de entrega.
- Una persona con el teléfono no escala a una ejecución nocturna, y nunca está ahí a las 3 de la madrugada.
El enfoque: un número real, leído por API
Pon una SIM tuya en un teléfono Android (o usa un iPhone con una automatización de Atajos), instala BusySMS y verifica el número. Registra tu cuenta de pruebas con ese número. En la prueba, dispara el SMS, pide a la API de BusySMS el siguiente código que llegue y escríbelo. El código es real, la entrega es real y nadie tiene que vigilar el teléfono.
- Sin códigos viejos. Una petición en espera solo devuelve un código que llegó después de empezar, así que una prueba nunca envía el código de la ejecución anterior.
- Una sola petición.
GET /api/v1/codes?timeoutMs=60000hace long-polling, así que no tienes que escribir ningún bucle de sondeo. - Filtros.
phone,serviceydeviceIdevitan que las pruebas en paralelo se quiten los códigos entre sí.
Configuración inicial
- 1
Registra un teléfono
Instala BusySMS desde Google Play en un teléfono con la SIM de pruebas, inicia sesión y verifica el número. La guía de la pasarela SMS en Android tiene los detalles.
- 2
Registra la cuenta de pruebas con ese número
Usa el número en el producto que estás probando, exactamente como lo haría un usuario.
- 3
Crea una clave de API
Panel → API REST → Crear clave. Cópiala en ese momento: solo se muestra una vez.
- 4
Guárdala como secreto de CI
Exponla al ejecutor de pruebas como
BUSYSMS_API_KEY. Nunca la subas al repositorio ni la envíes al navegador que estás probando.
Una pequeña función auxiliar
Todos los ejemplos de abajo siguen la misma idea: empezar a esperar, disparar el SMS y tomar el código. Pon la espera en una función auxiliar, en TypeScript o en Python:
const API = "https://busysms.net/api/v1/codes";
export async function waitForSmsCode(
opts: { phone?: string; service?: string; timeoutMs?: number } = {},
): Promise<string> {
const params = new URLSearchParams({ timeoutMs: String(opts.timeoutMs ?? 60_000) });
if (opts.phone) params.set("phone", opts.phone);
if (opts.service) params.set("service", opts.service);
const res = await fetch(`${API}?${params}`, {
headers: { Authorization: `Bearer ${process.env.BUSYSMS_API_KEY}` },
});
if (!res.ok) throw new Error(`BusySMS ${res.status}: ${await res.text()}`);
const { code } = (await res.json()) as { code: string | null };
if (!code) throw new Error("No new SMS code arrived in time");
return code;
}import os
import requests
API = "https://busysms.net/api/v1/codes"
def wait_for_sms_code(phone=None, service=None, timeout_ms=60_000):
params = {"timeoutMs": timeout_ms}
if phone:
params["phone"] = phone
if service:
params["service"] = service
r = requests.get(
API,
params=params,
headers={"Authorization": f"Bearer {os.environ['BUSYSMS_API_KEY']}"},
timeout=timeout_ms / 1000 + 15,
)
r.raise_for_status()
code = r.json()["code"]
if not code:
raise TimeoutError("No new SMS code arrived in time")
return codeEmpieza la espera antes de hacer clic en Enviar código. Así se evita la carrera en la que un SMS rápido llega antes de que empieces a esperar. Un código que llegó hasta un segundo antes de la petición todavía se trata como nuevo, pero uno más antiguo no.
Playwright
import { test, expect } from "@playwright/test";
import { waitForSmsCode } from "./busysms";
test("signs in with SMS two-factor", async ({ page }) => {
test.setTimeout(120_000); // the default 30 s is shorter than an SMS wait
await page.goto("https://staging.example.com/login");
await page.getByLabel("Phone number").fill("+44 7700 900123");
// Start waiting BEFORE the SMS is sent, then trigger it.
const code = waitForSmsCode({ phone: "447700900123" });
await page.getByRole("button", { name: "Send code" }).click();
await page.getByLabel("Verification code").fill(await code);
await page.getByRole("button", { name: "Verify" }).click();
await expect(page).toHaveURL(/dashboard/);
});Selenium (Python)
Las llamadas de Selenium son bloqueantes, así que ejecuta la espera en un hilo aparte, dispara el SMS y luego recoge el resultado:
from concurrent.futures import ThreadPoolExecutor
from selenium import webdriver
from selenium.webdriver.common.by import By
from busysms import wait_for_sms_code
driver = webdriver.Chrome()
driver.get("https://staging.example.com/login")
driver.find_element(By.NAME, "phone").send_keys("+44 7700 900123")
with ThreadPoolExecutor(max_workers=1) as pool:
# Start waiting first, then trigger the SMS.
pending = pool.submit(wait_for_sms_code, phone="447700900123")
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
code = pending.result()
driver.find_element(By.NAME, "otp").send_keys(code)
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()Cypress
Los comandos de Cypress se ejecutan en el navegador, así que mantén la clave fuera de él: llama a la API desde una tarea de Node en cypress.config.ts y usa el resultado en la prueba.
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
setupNodeEvents(on) {
on("task", {
// Runs in Node, so the API key never reaches the browser.
async waitForSmsCode({ phone }: { phone: string }) {
const params = new URLSearchParams({ phone, timeoutMs: "60000" });
const res = await fetch(`https://busysms.net/api/v1/codes?${params}`, {
headers: { Authorization: `Bearer ${process.env.BUSYSMS_API_KEY}` },
});
if (!res.ok) throw new Error(`BusySMS ${res.status}`);
const { code } = await res.json();
return code; // null if nothing new arrived
},
});
},
},
});it("signs in with SMS two-factor", () => {
cy.visit("https://staging.example.com/login");
cy.get("input[name=phone]").type("+44 7700 900123");
cy.contains("button", "Send code").click();
cy.task("waitForSmsCode", { phone: "447700900123" }, { timeout: 90_000 }).then((code) => {
expect(code, "SMS code").to.be.a("string");
cy.get("input[name=otp]").type(String(code));
});
cy.contains("button", "Verify").click();
});Cypress ejecuta los comandos en orden, así que aquí la espera empieza justo después del clic. La entrega de un SMS rara vez es tan rápida, y un código que llegó hasta un segundo antes de la petición todavía cuenta.
Ejecuciones en paralelo y entregas inestables
- Un número por worker en paralelo es la regla más sencilla. Si los workers tienen que compartir un número, filtra por
servicey ejecuta de uno en uno los inicios de sesión en un mismo servicio. - Una espera tiene un límite de 90 segundos. Si un SMS puede tardar más, repite la espera una vez en lugar de subir el timeout.
- Cuando una prueba recibe
null, revisa el teléfono antes de culpar al producto:GET /api/v1/numbersinforma decanReceivey de unreasoncomogateway_offophone_unreachable. - Si nada está capturando cuando empieza una espera, BusySMS envía al teléfono una notificación pidiendo que se vuelva a encender la pasarela. Alguien tiene que tocarla.
- Los mensajes capturados se guardan hasta 7 días, tiempo suficiente para depurar la ejecución fallida de anoche.
Scripts simples y trabajos de CI
Nada de esto es específico de un framework de pruebas. Un paso de shell en un trabajo de CI puede hacer lo mismo con curl y jq: empieza la espera en segundo plano, dispara el SMS y luego lee el resultado.
# Start waiting in the background, then trigger the SMS
curl -s -H "Authorization: Bearer $BUSYSMS_API_KEY" \
"https://busysms.net/api/v1/codes?phone=447700900123&timeoutMs=60000" > code.json &
WAIT_PID=$!
./trigger-login.sh # whatever makes the service send the SMS
wait "$WAIT_PID"
CODE=$(jq -r .code code.json)
[ "$CODE" != "null" ] || { echo "No SMS code arrived" >&2; exit 1; }
echo "Got code $CODE"jq imprime null cuando no llegó ningún código, así que la comprobación hace fallar el trabajo en lugar de escribir la palabra null en un formulario.
Úsalo en cuentas que son tuyas
Esto funciona porque el código se envía a un número que es tuyo. Úsalo para cuentas que controlas: los usuarios de prueba de tu producto, tus propios accesos de administrador, tu entorno de staging. ¿Controlas el navegador con un agente de IA en lugar de un ejecutor de pruebas? La guía de MCP lo explica.
BusySMS lee los códigos enviados a tus propios números. No es una forma de saltarse la verificación de otra persona, y no está pensado para crear cuentas de forma masiva.
Dudas
Preguntas frecuentes
¿Cómo automatizo la verificación OTP en Selenium?
Lanza GET /api/v1/codes?timeoutMs=60000 en un hilo aparte, dispara el SMS y escribe el código devuelto en el campo. El ejemplo de Python de arriba hace exactamente eso.
¿Puedo automatizar OTP sin un teléfono real?
Con BusySMS no. Los códigos vienen de una SIM tuya en un teléfono real; los emuladores no se admiten como pasarela.
¿Una prueba puede recoger un código antiguo?
No. Con timeoutMs mayor que cero, la API solo devuelve códigos que llegaron después de iniciar la petición, y devuelve null si no llegó ninguno.
¿Cuánto cuesta usarlo en CI?
$5 por número verificado al mes tras 7 días de prueba gratis. El precio es por número, no por ejecución de pruebas ni por código.
¿Sirve para códigos por correo o de apps de autenticación?
No. BusySMS lee los códigos enviados por SMS a tus propios números. Los códigos por correo y los de apps de autenticación no son SMS, así que quedan fuera.
¿Todo mi equipo puede usar un mismo número de pruebas?
Sí. Comparte el número con hasta otros cinco usuarios de BusySMS; cuando acepten, sus propias claves de API también recibirán sus códigos nuevos.
Blog
Más del blog
Todos los artículosSigue leyendo
Guías relacionadas
Todas las guíasAPI de pasarela SMS
La referencia de la API de recepción: autenticación, endpoints, formato de respuesta, errores y código en curl, Node.js y Python.
Leer la guíaCódigos SMS para agentes de IA
Deja que Claude, Codex o cualquier cliente MCP espere códigos de verificación SMS a través del servidor MCP de BusySMS.
Leer la guíaPasarela SMS en Android
Convierte un teléfono Android tuyo en una pasarela cuyos códigos de verificación lees por API, MCP, el panel o Chrome.
Leer la guíaPruébalo con un número que ya tienes
Instala la app, verifica tu número y lee tu primer código hoy mismo. Cada cuenta empieza con 7 días de prueba gratis; después cuesta $5 por número verificado al mes, y puedes cancelar cuando quieras.
