theus-cli-linux-arm64 1.2.1 → 1.2.2
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 +15 -0
- package/RELEASE.json +6 -6
- package/bin/theus +0 -0
- package/package.json +1 -1
- package/vendor/chrome-extension/README.md +142 -0
- package/vendor/chrome-extension/background.js +540 -0
- package/vendor/chrome-extension/cdp-core.js +400 -0
- package/vendor/chrome-extension/cdp-core.test.ts +321 -0
- package/vendor/chrome-extension/cdp.js +672 -0
- package/vendor/chrome-extension/icons/icon128.png +0 -0
- package/vendor/chrome-extension/icons/icon16.png +0 -0
- package/vendor/chrome-extension/icons/icon32.png +0 -0
- package/vendor/chrome-extension/icons/icon48.png +0 -0
- package/vendor/chrome-extension/manifest.json +36 -0
- package/vendor/chrome-extension/popup.html +78 -0
- package/vendor/chrome-extension/popup.js +47 -0
- package/vendor/chrome-extension/recorder.js +316 -0
- package/vendor/theus-arsenal/.theus-plugin/plugin.json +1 -1
- package/vendor/theus-arsenal/README.md +1 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
// Efecto lateral: cdp-core.js cuelga TheusCdpCore de globalThis (mismo
|
|
3
|
+
// mecanismo que usa el service worker vía importScripts).
|
|
4
|
+
import './cdp-core.js'
|
|
5
|
+
|
|
6
|
+
type EntradaConsola = { ts: number; nivel: string; texto: string; url?: string }
|
|
7
|
+
type EntradaRed = {
|
|
8
|
+
id: string
|
|
9
|
+
metodo: string
|
|
10
|
+
url: string
|
|
11
|
+
tipo: string
|
|
12
|
+
estado: number | null
|
|
13
|
+
mime: string | null
|
|
14
|
+
tam: number | null
|
|
15
|
+
inicioMs: number
|
|
16
|
+
duracionMs: number | null
|
|
17
|
+
error: string | null
|
|
18
|
+
}
|
|
19
|
+
type EstadoRed = { entradas: EntradaRed[]; porId: Map<string, EntradaRed> }
|
|
20
|
+
type DefTecla = {
|
|
21
|
+
modifiers: number
|
|
22
|
+
key: string
|
|
23
|
+
code: string
|
|
24
|
+
keyCode: number
|
|
25
|
+
text?: string
|
|
26
|
+
}
|
|
27
|
+
type ResumenAX = {
|
|
28
|
+
elementos: Array<{
|
|
29
|
+
ref: string
|
|
30
|
+
backendDOMNodeId: number
|
|
31
|
+
rol: string
|
|
32
|
+
nombre: string
|
|
33
|
+
valor?: string
|
|
34
|
+
}>
|
|
35
|
+
truncado: boolean
|
|
36
|
+
totalCandidatos: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type TheusCdpCore = {
|
|
40
|
+
pushCircular<T>(buffer: T[], item: T, max: number): T | null
|
|
41
|
+
formatearEventoConsola(
|
|
42
|
+
method: string,
|
|
43
|
+
params: Record<string, unknown>,
|
|
44
|
+
ahoraMs?: number,
|
|
45
|
+
): EntradaConsola | null
|
|
46
|
+
crearEstadoRed(): EstadoRed
|
|
47
|
+
plegarEventoRed(
|
|
48
|
+
estado: EstadoRed,
|
|
49
|
+
method: string,
|
|
50
|
+
params: Record<string, unknown>,
|
|
51
|
+
max: number,
|
|
52
|
+
ahoraMs?: number,
|
|
53
|
+
): boolean
|
|
54
|
+
filtrarEntradas<T>(
|
|
55
|
+
entradas: T[],
|
|
56
|
+
opciones: {
|
|
57
|
+
pattern?: string
|
|
58
|
+
limit?: number
|
|
59
|
+
onlyErrors?: boolean
|
|
60
|
+
campoTexto: string
|
|
61
|
+
},
|
|
62
|
+
): T[]
|
|
63
|
+
parsearCombinacionTeclas(combo: string): DefTecla | null
|
|
64
|
+
resumirArbolAX(
|
|
65
|
+
nodos: unknown[],
|
|
66
|
+
filtro: string | undefined,
|
|
67
|
+
maxElementos: number,
|
|
68
|
+
): ResumenAX
|
|
69
|
+
planRecompresionCaptura(
|
|
70
|
+
base64Length: number,
|
|
71
|
+
anchoPx: number,
|
|
72
|
+
opciones?: { maxAncho?: number; maxBase64?: number },
|
|
73
|
+
): { reprocesar: boolean; escala?: number; calidad?: number }
|
|
74
|
+
horaLocal(ms: number): string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const Core = (globalThis as unknown as { TheusCdpCore: TheusCdpCore }).TheusCdpCore
|
|
78
|
+
|
|
79
|
+
describe('buffers circulares', () => {
|
|
80
|
+
test('expulsa el más antiguo al desbordar y lo devuelve', () => {
|
|
81
|
+
const buffer: number[] = []
|
|
82
|
+
expect(Core.pushCircular(buffer, 1, 2)).toBeNull()
|
|
83
|
+
expect(Core.pushCircular(buffer, 2, 2)).toBeNull()
|
|
84
|
+
expect(Core.pushCircular(buffer, 3, 2)).toBe(1)
|
|
85
|
+
expect(buffer).toEqual([2, 3])
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('formatearEventoConsola', () => {
|
|
90
|
+
test('consoleAPICalled une los argumentos', () => {
|
|
91
|
+
const entrada = Core.formatearEventoConsola(
|
|
92
|
+
'Runtime.consoleAPICalled',
|
|
93
|
+
{
|
|
94
|
+
type: 'warning',
|
|
95
|
+
args: [
|
|
96
|
+
{ type: 'string', value: 'hola' },
|
|
97
|
+
{ type: 'number', value: 42 },
|
|
98
|
+
{ type: 'object', description: 'Object' },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
1000,
|
|
102
|
+
)!
|
|
103
|
+
expect(entrada.nivel).toBe('warning')
|
|
104
|
+
expect(entrada.texto).toBe('hola 42 Object')
|
|
105
|
+
expect(entrada.ts).toBe(1000)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('exceptionThrown es nivel error con detalle y url', () => {
|
|
109
|
+
const entrada = Core.formatearEventoConsola('Runtime.exceptionThrown', {
|
|
110
|
+
exceptionDetails: {
|
|
111
|
+
text: 'Uncaught',
|
|
112
|
+
url: 'https://ejemplo.pe/app.js',
|
|
113
|
+
exception: { description: 'TypeError: x is not a function' },
|
|
114
|
+
},
|
|
115
|
+
})!
|
|
116
|
+
expect(entrada.nivel).toBe('error')
|
|
117
|
+
expect(entrada.texto).toBe('Uncaught TypeError: x is not a function')
|
|
118
|
+
expect(entrada.url).toBe('https://ejemplo.pe/app.js')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('Log.entryAdded conserva nivel y texto; otros eventos devuelven null', () => {
|
|
122
|
+
const entrada = Core.formatearEventoConsola('Log.entryAdded', {
|
|
123
|
+
entry: { level: 'error', text: 'Mixed Content', url: 'https://x.pe' },
|
|
124
|
+
})!
|
|
125
|
+
expect(entrada.nivel).toBe('error')
|
|
126
|
+
expect(entrada.texto).toBe('Mixed Content')
|
|
127
|
+
expect(Core.formatearEventoConsola('Network.responseReceived', {})).toBeNull()
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('plegarEventoRed', () => {
|
|
132
|
+
test('pliega el ciclo completo de una petición', () => {
|
|
133
|
+
const estado = Core.crearEstadoRed()
|
|
134
|
+
Core.plegarEventoRed(
|
|
135
|
+
estado,
|
|
136
|
+
'Network.requestWillBeSent',
|
|
137
|
+
{
|
|
138
|
+
requestId: 'r1',
|
|
139
|
+
type: 'XHR',
|
|
140
|
+
request: { method: 'POST', url: 'https://api.pe/v1' },
|
|
141
|
+
},
|
|
142
|
+
10,
|
|
143
|
+
1000,
|
|
144
|
+
)
|
|
145
|
+
Core.plegarEventoRed(
|
|
146
|
+
estado,
|
|
147
|
+
'Network.responseReceived',
|
|
148
|
+
{ requestId: 'r1', response: { status: 201, mimeType: 'application/json' } },
|
|
149
|
+
10,
|
|
150
|
+
)
|
|
151
|
+
Core.plegarEventoRed(
|
|
152
|
+
estado,
|
|
153
|
+
'Network.loadingFinished',
|
|
154
|
+
{ requestId: 'r1', encodedDataLength: 512 },
|
|
155
|
+
10,
|
|
156
|
+
1350,
|
|
157
|
+
)
|
|
158
|
+
expect(estado.entradas).toHaveLength(1)
|
|
159
|
+
const e = estado.entradas[0]!
|
|
160
|
+
expect(e.metodo).toBe('POST')
|
|
161
|
+
expect(e.estado).toBe(201)
|
|
162
|
+
expect(e.mime).toBe('application/json')
|
|
163
|
+
expect(e.tam).toBe(512)
|
|
164
|
+
expect(e.duracionMs).toBe(350)
|
|
165
|
+
expect(e.error).toBeNull()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('loadingFailed registra el error', () => {
|
|
169
|
+
const estado = Core.crearEstadoRed()
|
|
170
|
+
Core.plegarEventoRed(
|
|
171
|
+
estado,
|
|
172
|
+
'Network.requestWillBeSent',
|
|
173
|
+
{ requestId: 'r2', request: { method: 'GET', url: 'https://caida.pe' } },
|
|
174
|
+
10,
|
|
175
|
+
)
|
|
176
|
+
Core.plegarEventoRed(
|
|
177
|
+
estado,
|
|
178
|
+
'Network.loadingFailed',
|
|
179
|
+
{ requestId: 'r2', errorText: 'net::ERR_CONNECTION_REFUSED' },
|
|
180
|
+
10,
|
|
181
|
+
)
|
|
182
|
+
expect(estado.entradas[0]!.error).toBe('net::ERR_CONNECTION_REFUSED')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('al expulsar una entrada del buffer circular limpia su índice', () => {
|
|
186
|
+
const estado = Core.crearEstadoRed()
|
|
187
|
+
for (let i = 0; i < 3; i++) {
|
|
188
|
+
Core.plegarEventoRed(
|
|
189
|
+
estado,
|
|
190
|
+
'Network.requestWillBeSent',
|
|
191
|
+
{ requestId: `r${i}`, request: { method: 'GET', url: `https://p.pe/${i}` } },
|
|
192
|
+
2,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
expect(estado.entradas).toHaveLength(2)
|
|
196
|
+
expect(estado.porId.has('r0')).toBe(false)
|
|
197
|
+
expect(estado.porId.has('r2')).toBe(true)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('filtrarEntradas', () => {
|
|
202
|
+
const consola: EntradaConsola[] = [
|
|
203
|
+
{ ts: 1, nivel: 'log', texto: '[MiApp] arranque' },
|
|
204
|
+
{ ts: 2, nivel: 'error', texto: '[MiApp] fallo de red' },
|
|
205
|
+
{ ts: 3, nivel: 'log', texto: 'ruido de terceros' },
|
|
206
|
+
{ ts: 4, nivel: 'error', texto: 'otro fallo' },
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
test('filtra por regex y corta a las más recientes', () => {
|
|
210
|
+
const r = Core.filtrarEntradas(consola, {
|
|
211
|
+
pattern: 'MiApp',
|
|
212
|
+
limit: 1,
|
|
213
|
+
campoTexto: 'texto',
|
|
214
|
+
})
|
|
215
|
+
expect(r).toHaveLength(1)
|
|
216
|
+
expect(r[0]!.texto).toBe('[MiApp] fallo de red')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('onlyErrors deja solo errores; sin limit usa 100 por defecto', () => {
|
|
220
|
+
const r = Core.filtrarEntradas(consola, { onlyErrors: true, campoTexto: 'texto' })
|
|
221
|
+
expect(r.map(e => e.ts)).toEqual([2, 4])
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('un pattern inválido lanza error honesto', () => {
|
|
225
|
+
let caught: unknown
|
|
226
|
+
try {
|
|
227
|
+
Core.filtrarEntradas(consola, { pattern: '(', campoTexto: 'texto' })
|
|
228
|
+
} catch (e) {
|
|
229
|
+
caught = e
|
|
230
|
+
}
|
|
231
|
+
expect(caught instanceof Error).toBe(true)
|
|
232
|
+
expect((caught as Error).message).toContain('regex')
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe('parsearCombinacionTeclas', () => {
|
|
237
|
+
test('tecla simple imprime texto', () => {
|
|
238
|
+
const def = Core.parsearCombinacionTeclas('a')!
|
|
239
|
+
expect(def).toMatchObject({ modifiers: 0, key: 'a', code: 'KeyA', keyCode: 65 })
|
|
240
|
+
expect(def.text).toBe('a')
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test('ctrl+a no imprime texto y lleva el modificador Ctrl', () => {
|
|
244
|
+
const def = Core.parsearCombinacionTeclas('ctrl+a')!
|
|
245
|
+
expect(def.modifiers).toBe(2)
|
|
246
|
+
expect(def.keyCode).toBe(65)
|
|
247
|
+
expect(def.text).toBeUndefined()
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('ctrl+shift+t combina modificadores y sube a mayúscula', () => {
|
|
251
|
+
const def = Core.parsearCombinacionTeclas('ctrl+shift+t')!
|
|
252
|
+
expect(def.modifiers).toBe(10)
|
|
253
|
+
expect(def.key).toBe('T')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('Enter y F5 se reconocen; lo desconocido devuelve null', () => {
|
|
257
|
+
expect(Core.parsearCombinacionTeclas('Enter')!.keyCode).toBe(13)
|
|
258
|
+
expect(Core.parsearCombinacionTeclas('F5')!.keyCode).toBe(116)
|
|
259
|
+
expect(Core.parsearCombinacionTeclas('teclarara')).toBeNull()
|
|
260
|
+
expect(Core.parsearCombinacionTeclas('')).toBeNull()
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
describe('resumirArbolAX', () => {
|
|
265
|
+
const nodos = [
|
|
266
|
+
{
|
|
267
|
+
backendDOMNodeId: 11,
|
|
268
|
+
role: { value: 'button' },
|
|
269
|
+
name: { value: 'Enviar' },
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
backendDOMNodeId: 12,
|
|
273
|
+
role: { value: 'textbox' },
|
|
274
|
+
name: { value: 'Correo' },
|
|
275
|
+
value: { value: 'a@b.pe' },
|
|
276
|
+
},
|
|
277
|
+
{ backendDOMNodeId: 13, role: { value: 'heading' }, name: { value: 'Título' } },
|
|
278
|
+
{ backendDOMNodeId: 14, role: { value: 'GenericContainer' }, name: { value: '' } },
|
|
279
|
+
{ backendDOMNodeId: 15, role: { value: 'link' }, name: { value: 'Inicio' }, ignored: true },
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
test('interactive (por defecto) solo devuelve interactivos con refs 1..n', () => {
|
|
283
|
+
const r = Core.resumirArbolAX(nodos, undefined, 150)
|
|
284
|
+
expect(r.elementos.map(e => e.rol)).toEqual(['button', 'textbox'])
|
|
285
|
+
expect(r.elementos.map(e => e.ref)).toEqual(['1', '2'])
|
|
286
|
+
expect(r.elementos[1]!.valor).toBe('a@b.pe')
|
|
287
|
+
expect(r.truncado).toBe(false)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
test('all añade estructura pero sigue ignorando nodos ignored y genéricos', () => {
|
|
291
|
+
const r = Core.resumirArbolAX(nodos, 'all', 150)
|
|
292
|
+
expect(r.elementos.map(e => e.rol)).toEqual(['button', 'textbox', 'heading'])
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test('trunca honestamente y lo declara', () => {
|
|
296
|
+
const r = Core.resumirArbolAX(nodos, undefined, 1)
|
|
297
|
+
expect(r.elementos).toHaveLength(1)
|
|
298
|
+
expect(r.truncado).toBe(true)
|
|
299
|
+
expect(r.totalCandidatos).toBe(2)
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe('planRecompresionCaptura', () => {
|
|
304
|
+
test('no reprocesa capturas pequeñas y estrechas', () => {
|
|
305
|
+
expect(Core.planRecompresionCaptura(1000, 800)).toEqual({ reprocesar: false })
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
test('escala cuando excede el ancho máximo', () => {
|
|
309
|
+
const plan = Core.planRecompresionCaptura(1000, 3200, { maxAncho: 1600 })
|
|
310
|
+
expect(plan.reprocesar).toBe(true)
|
|
311
|
+
expect(plan.escala).toBe(0.5)
|
|
312
|
+
expect(plan.calidad).toBe(0.8)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test('baja la calidad cuando excede el tamaño del canal', () => {
|
|
316
|
+
const plan = Core.planRecompresionCaptura(13 * 1024 * 1024, 1200)
|
|
317
|
+
expect(plan.reprocesar).toBe(true)
|
|
318
|
+
expect(plan.escala).toBe(1)
|
|
319
|
+
expect(plan.calidad).toBe(0.6)
|
|
320
|
+
})
|
|
321
|
+
})
|