@gnpdev/rpa-tools 1.0.9 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -3
- package/package.json +2 -2
- package/src/errorCapture.js +149 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +2 -0
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Recupera de forma segura el `usuario`, `password` e `idUsuario` de una aplicaci
|
|
|
68
68
|
import { logger, rpa } from './lib/rpa.js';
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
logger.info('
|
|
71
|
+
logger.info('Obtener credenciales');
|
|
72
72
|
const credentials = await rpa.getCredentials('Portal CRM');
|
|
73
73
|
|
|
74
74
|
if (credentials) {
|
|
@@ -81,7 +81,7 @@ if (credentials) {
|
|
|
81
81
|
Activa el watcher pasando la instancia de `page`. El bot detectará cambios en la tabla de la DB para tomar screenshots automáticamente.
|
|
82
82
|
|
|
83
83
|
```javascript
|
|
84
|
-
|
|
84
|
+
import { chromium } from 'playwright';
|
|
85
85
|
import { rpa } from './lib/rpa.js';
|
|
86
86
|
|
|
87
87
|
const browser = await chromium.launch();
|
|
@@ -90,8 +90,57 @@ const page = await browser.newPage();
|
|
|
90
90
|
// Inicia el monitoreo (polling cada 3s por defecto)
|
|
91
91
|
rpa.watchDebugFlag(page);
|
|
92
92
|
|
|
93
|
+
|
|
93
94
|
// Al terminar el proceso del bot
|
|
94
|
-
|
|
95
|
+
rpa.destroy();
|
|
96
|
+
|
|
97
|
+
puedes incluir
|
|
98
|
+
|
|
99
|
+
browser.on('disconnected', () => rpa.destroy());
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 4. Captura Automática de Errores (Screenshot + Trace)
|
|
103
|
+
Captura el estado completo del bot cuando ocurre una excepción: Screenshot (.webp), Trace de Playwright (.zip) y registro en la base de datos `bots.tb_error_bots`.
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
import { chromium } from 'playwright';
|
|
107
|
+
import { rpa, logger } from './lib/rpa.js';
|
|
108
|
+
|
|
109
|
+
const browser = await chromium.launch({ headless: true });
|
|
110
|
+
const context = await browser.newContext();
|
|
111
|
+
const page = await context.newPage();
|
|
112
|
+
|
|
113
|
+
// ── Activar tracing ANTES de que empiece la sesión ──────────────────────
|
|
114
|
+
// screenshots:true → captura visual en cada snapshot
|
|
115
|
+
// snapshots:true → guarda el DOM y estado de red en cada paso
|
|
116
|
+
await context.tracing.start({
|
|
117
|
+
screenshots: true,
|
|
118
|
+
snapshots: true,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
let currentStep = 'Inicio';
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Lógica de tu bot...
|
|
125
|
+
await page.goto('https://example.com');
|
|
126
|
+
|
|
127
|
+
currentStep = 'Login';
|
|
128
|
+
await login(page);
|
|
129
|
+
|
|
130
|
+
currentStep = 'Procesar Datos';
|
|
131
|
+
await page.click('#boton-inexistente');
|
|
132
|
+
} catch (err) {
|
|
133
|
+
// Captura automática de evidencia
|
|
134
|
+
const { errorId } = await rpa.captureError({
|
|
135
|
+
page,
|
|
136
|
+
context,
|
|
137
|
+
err,
|
|
138
|
+
step: currentStep
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
logger.error({ errorId, step: currentStep }, 'Fallo crítico capturado');
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
95
144
|
```
|
|
96
145
|
|
|
97
146
|
## Variables de Entorno Sugeridas
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gnpdev/rpa-tools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Libreria para logs y screenshot de bots",
|
|
5
5
|
"author": "Sergio Antonio Trujillo del Valle",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"minio": "^8.0.7",
|
|
21
21
|
"pino": "^10.3.1",
|
|
22
|
+
"pino-pretty": "^13.1.3",
|
|
22
23
|
"sharp": "^0.34.5"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
@@ -26,7 +27,6 @@
|
|
|
26
27
|
"@commitlint/config-conventional": "^20.5.0",
|
|
27
28
|
"@release-it/conventional-changelog": "^10.0.6",
|
|
28
29
|
"husky": "^9.1.7",
|
|
29
|
-
"pino-pretty": "^13.1.3",
|
|
30
30
|
"release-it": "^19.2.4"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs/promises');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sube un buffer o archivo local a MinIO y retorna el key generado.
|
|
7
|
+
* @param {import('minio').Client} client
|
|
8
|
+
* @param {string} bucket
|
|
9
|
+
* @param {string} key
|
|
10
|
+
* @param {Buffer|string} source - Buffer o ruta de archivo local
|
|
11
|
+
* @param {string} contentType
|
|
12
|
+
*/
|
|
13
|
+
async function uploadToMinio(client, bucket, key, source, contentType) {
|
|
14
|
+
const buffer = typeof source === 'string'
|
|
15
|
+
? await fs.readFile(source)
|
|
16
|
+
: source;
|
|
17
|
+
|
|
18
|
+
await client.putObject(bucket, key, buffer, buffer.length, {
|
|
19
|
+
'Content-Type': contentType,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return key;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Genera el prefijo base para los archivos de error en MinIO.
|
|
27
|
+
* Estructura: errors/{botId}/{YYYY-MM-DD}/{ISO-timestamp}
|
|
28
|
+
*/
|
|
29
|
+
function errorPrefix(botId) {
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const date = now.toISOString().slice(0, 10);
|
|
32
|
+
const ts = now.toISOString().replace(/[:.]/g, '-');
|
|
33
|
+
return `errors/${botId}/${date}/${ts}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Captura el estado completo cuando ocurre un error:
|
|
38
|
+
* - Screenshot WebP de la página actual
|
|
39
|
+
* - Trace de Playwright (.zip con video, red, consola, DOM snapshots)
|
|
40
|
+
* - Registro en bots.tb_error_bots
|
|
41
|
+
*
|
|
42
|
+
* @param {object} p
|
|
43
|
+
* @param {import('playwright').Page} p.page
|
|
44
|
+
* @param {import('playwright').BrowserContext} p.context
|
|
45
|
+
* @param {Error} p.err
|
|
46
|
+
* @param {string} p.botId - UUID del bot
|
|
47
|
+
* @param {import('pg').Pool} p.pool
|
|
48
|
+
* @param {import('minio').Client} p.minioClient
|
|
49
|
+
* @param {string} p.bucket
|
|
50
|
+
* @param {import('pino').Logger} p.logger
|
|
51
|
+
* @param {string} [p.step] - paso donde falló
|
|
52
|
+
*
|
|
53
|
+
* @returns {Promise<{ screenshotKey: string|null, traceKey: string|null, errorId: number|null }>}
|
|
54
|
+
*/
|
|
55
|
+
async function captureError({ page, context, err, botId, pool, minioClient, bucket, logger, step }) {
|
|
56
|
+
const prefix = errorPrefix(botId);
|
|
57
|
+
const stepInfo = step ? { step } : {};
|
|
58
|
+
const result = { screenshotKey: null, traceKey: null, errorId: null };
|
|
59
|
+
|
|
60
|
+
// ── 1. Screenshot ────────────────────────────────────────────────────────
|
|
61
|
+
try {
|
|
62
|
+
const buffer = await page.screenshot({
|
|
63
|
+
type: 'jpeg',
|
|
64
|
+
quality: 90,
|
|
65
|
+
fullPage: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let finalBuffer = buffer;
|
|
69
|
+
let ext = 'jpg';
|
|
70
|
+
let contentType = 'image/jpeg';
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const sharp = require('sharp');
|
|
74
|
+
finalBuffer = await sharp(buffer).webp({ quality: 80 }).toBuffer();
|
|
75
|
+
ext = 'webp';
|
|
76
|
+
contentType = 'image/webp';
|
|
77
|
+
} catch {
|
|
78
|
+
// sharp no disponible, se sube en jpeg
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
result.screenshotKey = await uploadToMinio(
|
|
82
|
+
minioClient, bucket,
|
|
83
|
+
`${prefix}-error.${ext}`,
|
|
84
|
+
finalBuffer,
|
|
85
|
+
contentType
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
logger.info({ ...stepInfo, screenshotKey: result.screenshotKey }, 'Screenshot de error subido');
|
|
89
|
+
} catch (screenshotErr) {
|
|
90
|
+
logger.error({ err: screenshotErr }, 'No se pudo capturar screenshot de error');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── 2. Trace ─────────────────────────────────────────────────────────────
|
|
94
|
+
const tracePath = path.join(require('os').tmpdir(), `trace-${botId}-${Date.now()}.zip`);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await context.tracing.stop({ path: tracePath });
|
|
98
|
+
|
|
99
|
+
result.traceKey = await uploadToMinio(
|
|
100
|
+
minioClient, bucket,
|
|
101
|
+
`${prefix}-trace.zip`,
|
|
102
|
+
tracePath,
|
|
103
|
+
'application/zip'
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
logger.info({ ...stepInfo, traceKey: result.traceKey }, 'Trace de error subido');
|
|
107
|
+
} catch (traceErr) {
|
|
108
|
+
logger.error({ err: traceErr }, 'No se pudo capturar trace de error');
|
|
109
|
+
} finally {
|
|
110
|
+
await fs.unlink(tracePath).catch(() => {});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── 3. Registro en bots.tb_error_bots ───────────────────────────────────
|
|
114
|
+
try {
|
|
115
|
+
const { rows } = await pool.query(
|
|
116
|
+
`INSERT INTO bots.tb_error_bots
|
|
117
|
+
(bot_id, step, error_message, url, screenshot_key, trace_key)
|
|
118
|
+
VALUES ($1::uuid, $2, $3, $4, $5, $6)
|
|
119
|
+
RETURNING id`,
|
|
120
|
+
[
|
|
121
|
+
botId,
|
|
122
|
+
step ?? null,
|
|
123
|
+
err.message,
|
|
124
|
+
page.url() ?? null,
|
|
125
|
+
result.screenshotKey ?? null,
|
|
126
|
+
result.traceKey ?? null,
|
|
127
|
+
]
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
result.errorId = rows[0].id;
|
|
131
|
+
logger.info({ ...stepInfo, errorId: result.errorId }, 'Error registrado en BD');
|
|
132
|
+
} catch (dbErr) {
|
|
133
|
+
logger.error({ err: dbErr }, 'No se pudo registrar el error en BD');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── 4. Log estructurado final ────────────────────────────────────────────
|
|
137
|
+
logger.error({
|
|
138
|
+
...stepInfo,
|
|
139
|
+
err,
|
|
140
|
+
url: page.url(),
|
|
141
|
+
screenshotKey: result.screenshotKey,
|
|
142
|
+
traceKey: result.traceKey,
|
|
143
|
+
errorId: result.errorId,
|
|
144
|
+
}, 'Error capturado en playground');
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = { captureError };
|
package/src/index.d.ts
CHANGED
|
@@ -57,6 +57,22 @@ export interface RpaTools {
|
|
|
57
57
|
*/
|
|
58
58
|
watchDebugFlag: (page: Page, pollMs?: number) => void;
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Recupera credenciales de una aplicación desde la base de datos.
|
|
62
|
+
* @param nombre - Nombre de la aplicación (ej. 'Portal CRM')
|
|
63
|
+
*/
|
|
64
|
+
getCredentials: (nombre: string) => Promise<{ usuario: string; password: string; idUsuario: string } | null>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Captura screenshot, trace y registra error en base de datos.
|
|
68
|
+
*/
|
|
69
|
+
captureError: (params: {
|
|
70
|
+
page: Page;
|
|
71
|
+
context: any;
|
|
72
|
+
err: Error;
|
|
73
|
+
step?: string;
|
|
74
|
+
}) => Promise<{ errorId: number | null; screenshotKey: string | null; traceKey: string | null }>;
|
|
75
|
+
|
|
60
76
|
/** Detiene todos los intervalos. Llamar siempre al cerrar el bot. */
|
|
61
77
|
destroy: () => void;
|
|
62
78
|
}
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@ const { createLogger } = require('./logger');
|
|
|
3
3
|
const { createMinioClient, ensureBucket } = require('./storage');
|
|
4
4
|
const { ScreenshotWatcher } = require('./watcher');
|
|
5
5
|
const { CredentialManager } = require('./credentials');
|
|
6
|
+
const { captureError } = require('./errorCapture');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Factory principal de la librería.
|
|
@@ -49,6 +50,7 @@ async function createRpaTools(opts = {}) {
|
|
|
49
50
|
logger,
|
|
50
51
|
watchDebugFlag: (page, pollMs) => watcher.watch(page, pollMs),
|
|
51
52
|
getCredentials: (nombre) => credentials.getAppCredentials(nombre),
|
|
53
|
+
captureError: ({ page, context, err, step }) => captureError({ page, context, err, step, botId, pool: db, minioClient, bucket, logger }),
|
|
52
54
|
destroy: () => watcher.destroy(),
|
|
53
55
|
};
|
|
54
56
|
}
|