Saltar al contenido principal
Proton v5

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:

  1. Variable de entorno: PROTON_HOST y PROTON_TOKEN.
  2. proton.ini en 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ónQué hace
evidence_dirCarpeta de evidencias; el valor predeterminado es evidencias, y None desactiva el envío
forward_logsCon False, el logging del proyecto no va al log de la ejecución
max_stepsLímite de pasos
close_at_endRecursos 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")
  • context es un dict opcional 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)
MotivoCuándo
FailureReason.BUSINESSLa regla de negocio no dejó seguir: un registro que ya existe, una factura ya rechazada. Repetir da el mismo resultado.
FailureReason.TECHNICALCualquier otro error: un elemento que no apareció, un sistema caído, una excepción del código. Es el predeterminado.
FailureReason.ENVIRONMENTEl 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óduloFunciónQué hace
proton.component_runnerrun_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_automationstart_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_parameterget_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_logsset_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_criticalEscribe 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_resourcesupload_image(caminho)Envía una imagen como evidencia
upload_file_resource(caminho)Envía un archivo como evidencia
proton.proton_environmentget_environment_value(nome) y otrasAmbiente de la ejecución: consulta Ambiente de la ejecución
proton.proton_envis_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_statusRunStatus y FailureReasonEstado y motivo de la falla
ProtonBusinessError y ProtonEnvironmentFailureExcepciones 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.