BusySMS

Automação de OTP

Como automatizar OTP e 2FA por SMS em testes e scripts

Códigos SMS de uso único são o passo que quebra os testes end-to-end. Este guia mostra como manter o passo real no teste: um chip real em um celular seu e uma chamada de API que espera o código, com exemplos funcionando para Playwright, Selenium e Cypress.

$5 por número verificado ao mês · 7 dias grátis · cobrança pelo Stripe na web

Nesta página

Por que o OTP quebra a automação

Um teste consegue clicar, digitar e verificar, mas não consegue ler um celular. Cada uma das gambiarras de sempre abre mão de alguma coisa:

  • Desligar o 2FA em staging significa que o caminho de login que os usuários realmente fazem nunca é testado.
  • Códigos de teste fixos só funcionam em sistemas que você controla, e escondem problemas de entrega.
  • Uma pessoa com o celular não escala para uma execução noturna, e nunca está lá às 3 da manhã.

A abordagem: um número real, lido por API

Coloque um chip seu em um celular Android (ou use um iPhone com uma automação do Atalhos), instale o BusySMS e verifique o número. Cadastre a sua conta de teste nesse número. No teste, dispare o SMS, peça à API do BusySMS o próximo código que chegar e digite-o. O código é real, o caminho de entrega é real, e ninguém precisa ficar de olho no celular.

  • Sem códigos velhos. Uma requisição em espera só retorna um código que chegou depois que ela começou, então um teste nunca envia o código da execução anterior.
  • Uma requisição. GET /api/v1/codes?timeoutMs=60000 faz long-polling, então não há loop de polling para escrever.
  • Filtros. phone, service e deviceId impedem que testes em paralelo peguem os códigos uns dos outros.

Configuração única

  1. 1

    Cadastre um celular

    Instale o BusySMS pelo Google Play em um celular com o chip de teste, entre e verifique o número. O guia do gateway de SMS no Android tem os detalhes.

  2. 2

    Cadastre a conta de teste nesse número

    Use o número no produto que você está testando, exatamente como um usuário faria.

  3. 3

    Crie uma chave de API

    Painel → API REST → Criar chave. Copie na hora: ela só aparece uma vez.

  4. 4

    Guarde-a como segredo de CI

    Exponha-a ao executor de testes como BUSYSMS_API_KEY. Nunca faça commit dela, e nunca a envie para o navegador sob teste.

Uma pequena função auxiliar

Todos os exemplos abaixo usam a mesma ideia: comece a esperar, dispare o SMS, pegue o código. Coloque a espera em uma função auxiliar, em TypeScript ou 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;
}

Comece a espera antes de clicar em Enviar código. Isso fecha a condição de corrida em que um SMS rápido chega antes de você começar a esperar. Um código que chegou até cerca de um segundo antes da requisição ainda é tratado como novo, mas qualquer um mais antigo, não.

Playwright

login.spec.ts
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)

As chamadas do Selenium são bloqueantes, então rode a espera em uma thread separada, dispare o SMS e depois colete o resultado:

test_login.py
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

Os comandos do Cypress rodam no navegador, então mantenha a chave fora dele: chame a API a partir de uma task do Node em cypress.config.ts e use o resultado no teste.

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
        },
      });
    },
  },
});

O Cypress executa os comandos em ordem, então aqui a espera começa logo depois do clique. A entrega de SMS raramente é tão rápida, e um código que chegou até um segundo antes da requisição ainda conta.

Execuções em paralelo e entrega instável

  • Um número por worker em paralelo é a regra mais simples. Se os workers precisarem compartilhar um número, filtre por service e rode os logins no mesmo serviço um de cada vez.
  • Uma espera tem limite de 90 segundos. Se um SMS pode demorar mais, repita a espera uma vez em vez de aumentar o timeout.
  • Quando um teste recebe null, confira o celular antes de culpar o produto: GET /api/v1/numbers informa canReceive e um reason como gateway_off ou phone_unreachable.
  • Se nada estiver capturando quando uma espera começar, o BusySMS manda uma notificação para o celular pedindo que o gateway seja ligado de novo. Alguém precisa tocar nela.
  • As mensagens capturadas ficam guardadas por até 7 dias, tempo suficiente para depurar a execução que falhou ontem à noite.

Scripts simples e jobs de CI

Nada aqui é específico de um framework de testes. Um passo de shell em um job de CI faz o mesmo com curl e jq: comece a espera em segundo plano, dispare o SMS e depois leia o resultado.

bash
# 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"

O jq imprime null quando nenhum código chegou, então a checagem faz o job falhar em vez de digitar a palavra null em um formulário.

Use em contas que são suas

Isso funciona porque o código é enviado para um número que é seu. Use em contas que você controla: os usuários de teste do seu produto, seus próprios logins de administrador, o seu ambiente de staging. Vai controlar o navegador com um agente de IA em vez de um executor de testes? O guia de MCP explica como.

O BusySMS lê códigos enviados aos seus próprios números. Ele não é um jeito de burlar a verificação de outra pessoa, e não foi feito para criar contas em massa.

Dúvidas

Perguntas frequentes

Como automatizo a verificação por OTP no Selenium?

Inicie GET /api/v1/codes?timeoutMs=60000 em uma thread separada, dispare o SMS e digite o código retornado no campo. O exemplo em Python acima faz exatamente isso.

Posso automatizar OTP sem um celular de verdade?

Com o BusySMS, não. Os códigos vêm de um chip seu em um celular de verdade; emuladores não são aceitos como gateway.

Um teste pode pegar um código antigo?

Não. Com timeoutMs acima de zero, a API só retorna códigos que chegaram depois do início da requisição, e retorna null se nenhum chegou.

Quanto custa rodar no CI?

$5 por número verificado ao mês depois de 7 dias grátis. O preço é por número, não por execução de teste nem por código.

Funciona com códigos por e-mail ou apps autenticadores?

Não. O BusySMS lê códigos enviados por SMS para os seus próprios números. Códigos por e-mail e de apps autenticadores não são SMS, então ficam de fora.

Minha equipe toda pode usar um único número de teste?

Sim. Compartilhe o número com até cinco outros usuários do BusySMS; depois que eles aceitarem, as próprias chaves de API deles também recebem os códigos novos desse número.

Blog

Mais do blog

Todos os artigos

Continue lendo

Guias relacionados

Todos os guias

Teste com um número que é seu

Instale o app, verifique seu número e leia seu primeiro código hoje mesmo. Toda conta começa com 7 dias grátis; depois disso, são $5 por número verificado ao mês, e você pode cancelar quando quiser.