Proton Cloud SDK para Java
Artefacto br.com.protoncloud:protoncloud-sdk, para Java 21 o superior y Maven 3.9 o superior. Los
paquetes Java son br.com.atomicsolutions.proton y br.com.atomicsolutions.http.
Instalación
<dependency>
<groupId>br.com.protoncloud</groupId>
<artifactId>protoncloud-sdk</artifactId>
<version>5.5.0</version>
</dependency>
La SDK está en el repositorio Maven público de Atomic, sin credenciales. Declara el repositorio en el
pom.xml:
<repositories>
<repository>
<id>protoncloud</id>
<url>https://maven.protoncloud.com.br</url>
</repository>
</repositories>
Las versiones también siguen publicándose en el GitHub Packages de Atomic, que pide credenciales, para los proyectos que todavía apuntan allí.
Si vienes de proton-lib
Hasta las versiones 5.0.0-beta, la SDK se llamaba br.com.atomicsolutions.proton:proton-lib. Cambia solo el
groupId y el artifactId de la dependencia: los paquetes Java no cambiaron, así que ningún import
cambia.
Configuración
La dirección y el token se resuelven en este orden, de la mayor a la menor prioridad:
- System property:
-Dproton.host=...y-Dproton.token=.... - Variable de entorno:
PROTON_HOSTyPROTON_TOKEN. config.propertiesen el classpath.
Con el runner, el PROTON_HOST llega listo y el id de la ejecución viene en -DidDatasetRun. El runner
actualizado también entrega el token de la ejecución en PROTON_TOKEN, y la
SDK 5.2 lo usa aunque el config.properties tenga otro token. Mientras el archivo todavía tenga token, la
SDK avisa una vez en la salida de la ejecución.
Con un runner anterior, el token viene de la variable PROTON_TOKEN de la máquina del runner o del
config.properties, que también sirve para ejecutar fuera del runner, en la máquina de quien desarrolla:
hostname=https://app.protoncloud.com.br/api
token=<token>
El token puede venir con o sin el prefijo Bearer . Deja el archivo con el token fuera del control de
versiones.
Para apuntar el proyecto a otro servidor e ignorar lo que inyecta el runner, declara
proton.config.precedence = file en el config.properties. 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 registra una línea que dice cuál ganó (el token nunca se imprime).
El ciclo de una ejecución
El runner llama a mvn -B test -DidDatasetRun=<id>, con -Dtest=TestProtonScript cuando el proyecto tiene
esa clase. Desde la 5.3, esta prueba solo le entrega al
ejecutor de componentes la función que encuentra el componente por el
nombre:
import br.com.atomicsolutions.proton.ProtonComponentRunner;
import org.junit.jupiter.api.Test;
// La única clase del proyecto que importa la SDK
class TestProtonScript {
@Test
void executar() throws Exception {
ProtonComponentRunner.run(TestProtonScript::passo);
}
/** El paso del componente, o null cuando el componente no es de este proyecto. */
private static ProtonComponentRunner.Step passo(String nome) {
Componente componente = Componentes.localizar(nome);
return componente == null ? null : componente::executar;
}
}
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:
public class CriarPedido implements Componente {
@Override
public Map<String, String> executar(Map<String, String> parametros) {
String numero = new TelaDoPedido().criar(parametros.get("in_cliente"));
return Map.of("out_numero_pedido", numero);
}
}
Para fallar, el componente lanza una excepción. El ejecutor registra el stack trace original (incluso de
AssertionError y NoClassDefFoundError) 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 muestra el error, las causas y las
excepciones suprimidas sin las líneas de JUnit y de Surefire y sin los colores de la terminal.
Con opciones:
ProtonComponentRunner.of(TestProtonScript::passo)
.evidenceDir(Path.of("prints"))
.maxSteps(100)
.closeAtEnd(sessao)
.execute();
Fuera de una ejecución de Proton (sin -DidDatasetRun), el ejecutor lanza ProtonRunException en lugar de
llamar al servidor. ProtonEnv.isProtonExecution() indica si el proceso se ejecuta en una ejecución de
Proton.
Desde la 5.3, la SDK ya no incluye slf4j-simple: si usas SLF4J, declara el proveedor de log en tu propio
pom.xml. La salida de Maven se sigue subiendo como log de la ejecución.
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, ProtonComponentRunner.runEvidenceDir():
evidencias/<id de la ejecución>, o evidencias fuera de Proton.
Path print = ProtonComponentRunner.runEvidenceDir().resolve("pedido.png");
Sin evidenceDir(...), 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 closeAtEnd(...) (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. Así, el video de una ejecución que
pasó también llega a Proton:
try (WebSession web = WebSession.start()) {
ProtonComponentRunner.of(nome -> Passos.localizar(nome, web))
.closeAtEnd(web)
.execute();
}
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 agrega como excepción suprimida al error de ese paso. El try-with-resources
vuelve a cerrar el recurso, así que su close() tiene que aceptar que lo llamen dos veces.
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 ProtonComponentRunner.recordFailure (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.
WebSession web;
try {
web = WebSession.start();
} catch (RuntimeException e) {
ProtonComponentRunner.recordFailure(e, FailureReason.ENVIRONMENT);
throw e;
}
Fuera de Proton, o con la ejecución ya terminada, recordFailure no registra nada.
Sin el ejecutor
Los proyectos anteriores a la 5.3 recorren los pasos con las clases de la SDK. Un ejemplo simplificado:
import br.com.atomicsolutions.proton.ProtonAutomation;
import br.com.atomicsolutions.proton.ProtonLogs;
import br.com.atomicsolutions.proton.RunStatus;
import org.junit.jupiter.api.Test;
class TestProtonScript {
@Test
void runProtonExecution() {
ProtonAutomation.updateRunStatus(RunStatus.RUNNING);
try {
String nome;
while ((nome = ProtonAutomation.getCurrentComponentName()) != null && !nome.isEmpty()) {
ProtonAutomation.startComponent(); // abre el paso y lee el ambiente de la ejecución
Componentes.executar(nome); // el código del componente
ProtonAutomation.endComponent(); // cierra el paso como Passed
}
ProtonAutomation.updateRunStatus(RunStatus.PASSED);
} catch (RuntimeException e) {
ProtonLogs.setLog(e);
ProtonLogs.setErrorLog(e.getMessage());
ProtonAutomation.updateRunStatus(RunStatus.FAILED);
throw e;
}
}
}
Dentro de un componente, sin el ejecutor
import br.com.atomicsolutions.proton.ProtonEnvironment;
import br.com.atomicsolutions.proton.ProtonFilesAndResources;
import br.com.atomicsolutions.proton.ProtonParameter;
import java.nio.file.Path;
import static br.com.atomicsolutions.proton.ProtonLogs.setLog;
public static void criarPedido() {
String cliente = ProtonParameter.getProtonValue("CLIENTE"); // parámetro del dataset
String url = ProtonEnvironment.get("SISTEMA_URL"); // variable del ambiente de la ejecución
setLog("Creando pedido para " + cliente);
// ... automatización ...
ProtonFilesAndResources.uploadImage(Path.of("evidencias/pedido.png")); // evidencia del paso
ProtonParameter.setProtonValue("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 (ProtonLogs.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.
ProtonLogs.logInfo("Abriendo la transacción BP");
ProtonLogs.logWarning("El registro ya existe: pasando al siguiente", Map.of("cnpj", cnpj));
ProtonLogs.logError("Timeout en LoginSAP", Map.of("tentativa", 3));
ProtonLogs.setLog("Mensaje con el nivel elegido", ProtonLogs.CRITICAL);
- El contexto es un
Mapopcional 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. setLog(exceção)envía el stack trace completo como una línea de Error.- Cada línea también sale en la salida de Maven, 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):
HttpRequest.Builder pedido = HttpRequest.newBuilder(uri);
ProtonEnv.correlationHeaders().forEach(pedido::header);
Fuera de Proton, o con un runner que todavía no entrega el id, correlationHeaders() devuelve un mapa 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:
ProtonAutomation.updateRunStatus(RunStatus.FAILED, 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:
if (cadastroExiste) {
throw new ProtonBusinessError("El CNPJ " + cnpj + " ya está registrado");
}
El ejecutor busca el motivo en el error y en sus causas (FailureReason.of(erro), 5.5.0), y vale el primero
que indique el motivo:
ProtonBusinessError: falla de negocio;ProtonEnvironmentError(5.5.0) yProtonEnvironmentException(el ambiente de la ejecución sin la variable pedida): falla de ambiente;- la excepción que implementa
ProtonFailure(5.5.0): el motivo que ella indica; - cualquier otro error: falla técnica.
ProtonFailure sirve para la excepción que ya extiende otra clase, como la de una biblioteca, y por eso no
puede extender ProtonBusinessError ni ProtonEnvironmentError:
public class SessaoNaoAbriu extends RuntimeException implements ProtonFailure {
@Override
public String failureReason() {
return FailureReason.ENVIRONMENT;
}
}
Las constantes de Proton 4 (FAILED_DATA, FAILED_ENVIRONMENT e IN_PROCESS) están obsoletas: el
servidor las rechaza, y la SDK envía FAILED en lugar de las dos primeras, con el motivo de negocio y de
ambiente, y RUNNING en lugar de la última, con un aviso en la salida de la ejecución.
BrowserStack
En una ejecución en BrowserStack, el disparo de Proton elige el dispositivo, la versión del sistema y la
app. ProtonBrowserStack los entrega para abrir la sesión y registra en la ejecución el id de la sesión de
BrowserStack, para encontrar la ejecución en el panel de BrowserStack:
| Método | Qué hace |
|---|---|
getBStackDevice() | Nombre del dispositivo (Samsung Galaxy S23) |
getBStackDeviceOsVersion() | Versión del sistema elegida al disparar la ejecución (13.0); vacío con un Proton que todavía no la devuelve (5.5.0) |
getBStackAppReference() | App en BrowserStack (bs://...) |
recordBSSessionId(id) | Registra el id de la sesión en la ejecución y devuelve el estado HTTP: 200 significa que lo registró (5.5.0) |
int status = ProtonBrowserStack.recordBSSessionId(driver.getSessionId().toString());
if (status != 200) {
ProtonLogs.logWarning("Proton no registró la sesión de BrowserStack", Map.of("status", status));
}
updateBSSessionId(id) sigue funcionando, obsoleto, sin devolver el estado: cuando Proton no registra el id,
escribe una línea [proton] en la salida. El registro usa la ruta
PUT /dataset-run/{id}, y la versión del sistema viene
de deviceOsVersion en GET /dataset-run/{id}.
Referencia
| Clase | Método | Qué hace |
|---|---|---|
ProtonComponentRunner | run(resolver) y of(resolver) | Ejecuta los pasos del dataset con el ejecutor de componentes |
closeAtEnd(recursos...) | Cierra los recursos después del último paso, antes del estado (5.5.0) | |
runEvidenceDir() | Carpeta de evidencias de la ejecución (5.5.0) | |
recordFailure(erro) y recordFailure(erro, motivo) | Registra la falla que ocurrió antes del ejecutor (5.5.0) | |
ProtonAutomation | startComponent() | Abre el paso actual y lee el ambiente de la ejecución |
endComponent() | Cierra el paso actual | |
getCurrentComponentName() | Nombre del componente de turno; vacío cuando no hay más pasos | |
getCurrentComponentSystem() | Sistema del componente de turno | |
updateRunStatus(status) y updateRunStatus(status, motivo) | Marca la ejecución con un RunStatus y, en el FAILED, el motivo | |
getDatasetRunInfo() | Datos de la ejecución | |
ProtonParameter | getProtonValue(nome) | Valor de un parámetro del dataset en el paso abierto |
getProtonAllComponentParameters() | Todos los parámetros del paso abierto | |
setProtonValue(nome, valor) | Registra un parámetro de salida | |
getProtonOutputParameterList(nome) | Salidas registradas en la ejecución con ese nombre | |
ProtonLogs | setLog(texto) y setLog(texto, nível) | Escribe en el log de la ejecución, con el nivel |
logInfo, logWarning, logError y logCritical | Escribe con el nivel del nombre y un contexto opcional | |
setLog(texto, true) | Escribe en Markdown, sin fecha ni hora | |
setLog(exceção) | Escribe el stack trace en el log, como Error | |
setErrorLog(texto) | Registra el error de la ejecución | |
ProtonFilesAndResources | uploadImage(Path) | Envía una imagen como evidencia |
uploadVideo(Path) y uploadFileResource(Path) | Envía un video o un archivo como evidencia | |
ProtonEnvironment | get(nome) y otros | Ambiente de la ejecución: consulta Ambiente de la ejecución |
ProtonEnv | getHost(), getToken() y getIDDatasetRun() | Configuración del proceso |
isProtonExecution() | Si el proceso se ejecuta en una ejecución de Proton (5.5.0) | |
getCorrelationId() y correlationHeaders() | Correlation ID de la ejecución y los encabezados para propagarlo | |
FailureReason | BUSINESS, TECHNICAL y ENVIRONMENT | Motivo de la falla |
of(erro) | Motivo de un error, mirando también sus causas (5.5.0) | |
ProtonBrowserStack | getBStackDevice(), getBStackDeviceOsVersion() y getBStackAppReference() | Dispositivo, versión del sistema y app del disparo |
recordBSSessionId(id) | Registra el id de la sesión de BrowserStack en la ejecución (5.5.0) |
Errores de comunicación
Un error del servidor no interrumpe la automatización: la lectura vuelve vacía (getProtonValue devuelve
"" también cuando el parámetro no existe), y el registro de una salida que falla queda anotado en la salida
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 lanza
ProtonEnvironmentException, 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.