Proton Cloud SDK para Python
Paquete protoncloud-sdk en PyPI, para Python 3.10 o superior.
El nombre de import es proton.
Instalación
pip install protoncloud-sdk
O, con uv:
uv add protoncloud-sdk
Configuración
La dirección y el token se resuelven en este orden:
- Variable de entorno:
PROTON_HOSTyPROTON_TOKEN. proton.inien el directorio de ejecución.
Con el runner, el PROTON_HOST y el id de la ejecución (idDatasetRun) llegan listos. El runner
actualizado también entrega el token de la ejecución en PROTON_TOKEN, y la
SDK 5.2 lo usa aunque el proton.ini tenga otro token. Mientras el archivo todavía tenga token, la SDK
avisa una vez en el log.
Con un runner anterior, el token viene de la variable PROTON_TOKEN de la máquina del runner o del
proton.ini, que también sirve para ejecutar fuera del runner, en la máquina de quien desarrolla:
[server]
hostname = https://app.protoncloud.com.br/api
token = <token>
El token puede venir con o sin el prefijo Bearer . Deja el proton.ini con el token fuera del control de
versiones.
Para apuntar el proyecto a otro servidor e ignorar lo que inyecta el runner, usa config_precedence = file
en la sección [server]. Vale para la dirección y para el token personal, pero no para el token de la
ejecución. Sin esto, cuando el valor inyectado difiere del archivo, la SDK imprime una línea que dice cuál
ganó (el token nunca se imprime).
El ciclo de una ejecución
El runner llama a uv run pytest tests/test_proton_script.py::test_run_proton_execution --id_dataset_run=<id>.
Desde la 5.3, esta prueba solo le entrega al ejecutor de componentes la
función que encuentra el componente por el nombre:
# tests/test_proton_script.py: el único archivo del proyecto que importa la SDK
import os
from proton.component_runner import run_components
from automacao.apoio.passos import localizar # nombre del componente -> función executar(parametros)
def test_run_proton_execution(request):
os.environ["idDatasetRun"] = request.config.getoption("id_dataset_run")
run_components(localizar)
localizar recibe el nombre del componente y devuelve la función que lo ejecuta, o None cuando el
componente no es del proyecto. También sirve un dict de nombre a función. La opción --id_dataset_run se
registra en el conftest.py (parser.addoption).
El componente recibe los parámetros del paso, ya resueltos por Proton (variable, referencia a una salida, valor escrito al disparar la ejecución, cifrado), y devuelve las salidas:
# automacao/componentes/criar_pedido.py
def executar(parametros: dict) -> dict:
numero = TelaDoPedido().criar(parametros["in_cliente"])
return {"out_numero_pedido": numero}
Para fallar, el componente lanza una excepción. El ejecutor registra el stack trace en el panel de error y marca la ejecución como Failed, con el motivo de la falla. Desde la 5.5.0, el panel viene sin las líneas de pytest y sin los colores de la terminal.
Lo que el componente registra con logging (nivel INFO en adelante) va en vivo al log de la ejecución, con
su nivel: warning, error y critical pasan a ser Alerta, Error y Crítico.
Las opciones de run_components son:
| Opción | Qué hace |
|---|---|
evidence_dir | Carpeta de evidencias; el valor predeterminado es evidencias, y None desactiva el envío |
forward_logs | Con False, el logging del proyecto no va al log de la ejecución |
max_steps | Límite de pasos |
close_at_end | Recursos que el ejecutor cierra después del último paso (5.5.0) |
Fuera de una ejecución de Proton, run_components lanza ProtonRunError en lugar de llamar al servidor.
is_proton_execution() (de proton.proton_env) indica si el proceso se ejecuta en una ejecución de Proton;
desde la 5.5.0, exige un idDatasetRun no vacío.
Carpeta de evidencias
En cada paso, el ejecutor sube lo que apareció en la carpeta de evidencias desde el último envío: las imágenes van al paso y el resto a la ejecución. Lo que el proyecto guarda entre dos pasos se sube con el paso siguiente.
El runner puede procesar varias ejecuciones al mismo tiempo, en la misma carpeta del proyecto. Desde la
5.5.0, cada ejecución guarda en su propia carpeta, run_evidence_dir(): evidencias/<id de la ejecución>,
o evidencias fuera de Proton. La función devuelve la ruta y no crea la carpeta:
from proton.component_runner import run_evidence_dir
pasta = run_evidence_dir()
pasta.mkdir(parents=True, exist_ok=True)
navegador.save_screenshot(str(pasta / "pedido.png"))
En la carpeta predeterminada, el ejecutor revisa toda la carpeta evidencias menos las subcarpetas de las
otras ejecuciones: lo que la ejecución guarda en su carpeta se sube solo para ella, y lo que el proyecto
guarda directamente en evidencias se sigue subiendo, como antes.
Cerrar al final
El video y el trace de una sesión solo quedan listos cuando la sesión se cierra, después del último paso.
Con close_at_end (5.5.0), el ejecutor cierra los recursos, en el orden indicado y una sola vez, y sube lo
que apareció en la carpeta de evidencias antes de registrar el estado. Sirven objetos con close() o
funciones sin argumentos, como driver.quit. Así, el video de una ejecución que pasó también llega a Proton:
from proton.component_runner import run_components
navegador = abrir_navegador()
run_components(localizar, close_at_end=[navegador])
El ejecutor también cierra los recursos cuando un paso falla (antes del Failed) y cuando el siguiente paso es de otro proyecto. Un error al cerrar no cambia el resultado: si todo pasó, se convierte en una alerta en el log; si un paso falló, se convierte en una nota en el error de ese paso.
Falla antes del primer paso
La falla que ocurre antes del ejecutor, como la sesión que no se abre o el dispositivo fuera de servicio,
llega a Proton por record_failure (5.5.0): el error va al panel de error y el Failed se registra con el
motivo del error, o con el motivo indicado.
from proton.component_runner import record_failure
from proton.run_status import FailureReason
try:
navegador = abrir_navegador()
except Exception as erro:
record_failure(erro, FailureReason.ENVIRONMENT)
raise
Fuera de Proton, o con la ejecución ya terminada, record_failure no registra nada.
Sin el ejecutor
Los proyectos anteriores a la 5.3 recorren los pasos con las funciones de la SDK. Un ejemplo simplificado:
from proton.proton_automation import (
end_component,
get_current_component_name,
start_component,
update_run_status,
)
from proton.proton_logs import set_error_log, set_log_from_exception
from proton.run_status import RunStatus
from components import COMPONENTES # nombre del componente -> función con su código
def test_run_proton_execution(id_dataset_run):
update_run_status(RunStatus.RUNNING)
try:
while nome := get_current_component_name():
start_component() # abre el paso y lee el ambiente de la ejecución
COMPONENTES[nome]()
end_component() # cierra el paso como Passed
update_run_status(RunStatus.PASSED)
except Exception as erro:
set_log_from_exception(erro)
set_error_log(str(erro))
update_run_status(RunStatus.FAILED)
raise
El fixture id_dataset_run lee la opción --id_dataset_run y llama a set_id_dataset_run() (de
proton.proton_env). Sin la opción, la SDK queda inactiva y la prueba se ejecuta en local.
Dentro de un componente, sin el ejecutor
from proton.proton_environment import get_environment_value
from proton.proton_files_and_resources import upload_image
from proton.proton_logs import set_log
from proton.proton_parameter import get_proton_value, set_proton_value
def criar_pedido():
cliente = get_proton_value("CLIENTE") # parámetro del dataset
url = get_environment_value("SISTEMA_URL") # variable del ambiente de la ejecución
set_log(f"Creando pedido para {cliente}")
# ... automatización ...
upload_image("evidencias/pedido.png") # evidencia del paso
set_proton_value("NUMERO_PEDIDO", "4500012345") # parámetro de salida
Log de la ejecución
Desde la 5.4.0, cada línea del log tiene un nivel: Información (info, el predeterminado), Alerta
(warning), Error (error) o Crítico (critical). La pantalla de la ejecución filtra por nivel y destaca
alertas y errores, y la búsqueda en los logs encuentra la línea entre ejecuciones.
from proton.proton_logs import log_error, log_info, log_warning, set_log
log_info("Abriendo la transacción BP")
log_warning("El registro ya existe: pasando al siguiente", context={"cnpj": cnpj})
log_error("Timeout en LoginSAP", context={"tentativa": 3})
set_log("Mensaje con el nivel elegido", level="critical")
contextes undictopcional que va junto con la línea (el ítem procesado, el intento, el código de retorno). Los valores del ambiente también salen enmascarados en él.set_log_from_exception(erro)envía el stack trace completo como una línea de Error.- En una ejecución de Proton, cada línea también sale en la salida del proceso, con la hora y el nivel. El runner sube esa salida como el archivo de log de la ejecución: por encima del límite del log en Proton (10 mil líneas), es en ese archivo donde queda el log completo.
Correlation ID
Cada ejecución tiene un Correlation ID, el mismo que muestra la pantalla de Proton. El runner lo entrega en
PROTON_CORRELATION_ID (runner 5.0.8 en adelante), y la SDK lo envía en cada llamada a Proton
(X-Correlation-Id y traceparent, en el formato W3C Trace Context). Para llevarlo a las llamadas que hace
la automatización (SAP, APIs, portales), usa correlation_headers(). Con él, el log del sistema llamado se
encuentra por el id de la ejecución:
import requests
from proton.proton_env import correlation_headers, get_correlation_id
from proton.proton_logs import log_info
requests.post(url, json=pedido, headers={**correlation_headers(), "Authorization": token})
log_info(f"Pedido enviado ({get_correlation_id()})")
Fuera de Proton, o con un runner que todavía no entrega el id, correlation_headers() devuelve un
diccionario vacío.
Estado de la ejecución
Proton 5 acepta RunStatus.RUNNING, PASSED y FAILED. Desde la 5.4.0, el FAILED puede ir con el
motivo, que dice por qué falló la ejecución sin cambiar el estado:
from proton.proton_automation import update_run_status
from proton.run_status import FailureReason, RunStatus
update_run_status(RunStatus.FAILED, reason=FailureReason.BUSINESS)
| Motivo | Cuándo |
|---|---|
FailureReason.BUSINESS | La regla de negocio no dejó seguir: un registro que ya existe, una factura ya rechazada. Repetir da el mismo resultado. |
FailureReason.TECHNICAL | Cualquier otro error: un elemento que no apareció, un sistema caído, una excepción del código. Es el predeterminado. |
FailureReason.ENVIRONMENT | El ambiente no dejó ejecutar: una dependencia que falta, una configuración errónea. |
Con el ejecutor de componentes, basta con lanzar en el paso la excepción del motivo: ProtonBusinessError
para la falla de negocio y, desde la 5.5.0, ProtonEnvironmentFailure para la de ambiente (la sesión que no
se abre, el dispositivo fuera de servicio). Cualquier otra excepción cuenta como técnica.
from proton.run_status import ProtonBusinessError, ProtonEnvironmentFailure
if cadastro_existe:
raise ProtonBusinessError(f"El CNPJ {cnpj} ya está registrado")
try:
navegador = abrir_navegador()
except WebDriverException as erro:
raise ProtonEnvironmentFailure("El grid no abrió el navegador") from erro
Desde la 5.5.0, el ejecutor busca el motivo en el error y en sus causas (raise ... from causa), con la
función failure_reason (de proton.run_status), y vale el primero que indique el motivo. La excepción
indica el motivo con el atributo failure_reason, así que la excepción de una biblioteca que ya extiende
otra clase también puede indicarlo:
from proton.run_status import FailureReason
class SessaoNaoAbriu(RuntimeError):
failure_reason = FailureReason.ENVIRONMENT
El ProtonEnvironmentError del ambiente de la ejecución (la variable que falta)
también cuenta como falla de ambiente. En la SDK Java, la excepción de la falla de ambiente se llama
ProtonEnvironmentError; en Python ese nombre ya era el del error del ambiente de la ejecución, por eso
ProtonEnvironmentFailure.
Los estados de Proton 4 (FAILED_DATA, FAILED_ENVIRONMENT e IN_PROCESS) están obsoletos: el servidor
los rechaza, y la SDK envía FAILED en lugar de los dos primeros, con el motivo de negocio y de ambiente, y
RUNNING en lugar del último, con un DeprecationWarning y un aviso en el log la primera vez.
Referencia
| Módulo | Función | Qué hace |
|---|---|---|
proton.component_runner | run_components(localizar, ...) | Ejecuta los pasos del dataset con el ejecutor de componentes |
run_evidence_dir() | Carpeta de evidencias de la ejecución (5.5.0) | |
record_failure(erro, reason=None) | Registra la falla que ocurrió antes del ejecutor (5.5.0) | |
proton.proton_automation | start_component() | Abre el paso actual y lee el ambiente de la ejecución |
end_component() | Cierra el paso actual | |
get_current_component_name() | Nombre del componente de turno; vacío cuando no hay más pasos | |
get_current_component_system() | Sistema del componente de turno | |
update_run_status(status, reason=None) | Marca la ejecución con un RunStatus y, en el FAILED, el motivo | |
get_automation_name() y get_cycle_name() | Nombre de la automatización y del ciclo | |
get_dataset_run_status(id=None) | Estado de la ejecución (la actual, sin id) | |
get_dataset_run_info() | Datos de la ejecución; en una automatización mobile, también el dispositivo (device), dónde se ejecuta (deviceFarm) y la versión del sistema en BrowserStack (deviceOsVersion), estos dos en un Proton que ya los devuelve | |
get_dataset_info(id=None) | Datos del dataset (el de la ejecución, sin id) | |
get_automation_group_info(id) | Datos del grupo de automatización | |
proton.proton_parameter | get_proton_value(nome) | Valor de un parámetro del dataset en el paso abierto |
get_proton_all_component_parameters() | Todos los parámetros del paso abierto | |
set_proton_value(nome, valor) | Registra un parámetro de salida | |
get_proton_output_parameter_list(nome) | Salidas registradas en la ejecución con ese nombre | |
proton.proton_logs | set_log(texto, disable_date_time=False, level="info", context=None) | Escribe en el log de la ejecución, con el nivel; con disable_date_time=True, acepta Markdown sin fecha ni hora |
log_info, log_warning, log_error y log_critical | Escribe con el nivel del nombre y un contexto opcional | |
set_log_from_exception(erro) | Escribe el stack trace en el log, como Error | |
set_error_log(texto) | Registra el error de la ejecución | |
proton.proton_files_and_resources | upload_image(caminho) | Envía una imagen como evidencia |
upload_file_resource(caminho) | Envía un archivo como evidencia | |
proton.proton_environment | get_environment_value(nome) y otras | Ambiente de la ejecución: consulta Ambiente de la ejecución |
proton.proton_env | is_proton_execution() | Si la automatización se ejecuta desde Proton |
get_id_dataset_run() y set_id_dataset_run(id) | Id de la ejecución | |
get_correlation_id() y correlation_headers() | Correlation ID de la ejecución y los encabezados para propagarlo | |
proton.run_status | RunStatus y FailureReason | Estado y motivo de la falla |
ProtonBusinessError y ProtonEnvironmentFailure | Excepciones que marcan la falla de negocio y la de ambiente | |
failure_reason(erro) | Motivo de un error, mirando también sus causas (5.5.0) |
Errores de comunicación
Un error del servidor no interrumpe la automatización: la lectura vuelve vacía (get_proton_value devuelve
"" también cuando el parámetro no existe) y las llamadas de control imprimen una línea [proton] con la
ruta y el estado HTTP, que el runner lleva al log de la ejecución. Cuando un valor venga vacío sin motivo,
empieza por el log.
La excepción es el ambiente de la ejecución: leer una variable que no existe detiene el script con
ProtonEnvironmentError, porque seguir con un valor vacío escondería el problema. Desde la 5.5.0, en el
ejecutor de componentes, cuenta como falla de ambiente.