@diegosouzacdv/jev-browser-mcp 0.3.0 → 0.4.0

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
3
  "browser": {
4
- "mode": "harness",
4
+ "mode": "computer",
5
5
  "harness_browser": "chrome",
6
6
  "playwright_mcp_package": "@playwright/mcp@0.0.79",
7
7
  "computer_browser": "chrome",
@@ -22,8 +22,20 @@
22
22
  },
23
23
  "jev_browser_mcp": {
24
24
  "browser": {
25
- "max_action_timeout_seconds": 8,
26
- "max_upload_files": 5,
25
+ "max_action_timeout_seconds": 8,
26
+ "ready_timeout_seconds_default": 15,
27
+ "max_ready_timeout_seconds": 60,
28
+ "ready_network_idle_default": true,
29
+ "ready_stable_ms_default": 400,
30
+ "max_ready_stable_ms": 2000,
31
+ "post_step_ready_timeout_seconds_default": 5,
32
+ "max_step_timeout_seconds": 60,
33
+ "key_delay_ms_default": 30,
34
+ "reuse_page_default": false,
35
+ "capture_network_error_bodies_default": false,
36
+ "max_network_error_body_bytes": 65536,
37
+ "max_network_error_message_chars": 300,
38
+ "max_upload_files": 5,
27
39
  "max_upload_path_chars": 4096,
28
40
  "max_upload_file_bytes": 10485760,
29
41
  "max_upload_total_bytes": 26214400,
@@ -17,10 +17,10 @@ versão em produção, use o número explícito no argumento do pacote:
17
17
  "mcpServers": {
18
18
  "jev-browser": {
19
19
  "command": "npx",
20
- "args": ["--yes", "@diegosouzacdv/jev-browser-mcp@0.3.0"],
20
+ "args": ["--yes", "@diegosouzacdv/jev-browser-mcp@0.4.0"],
21
21
  "env": {
22
22
  "OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
23
- "JEV_BROWSER_MODE": "harness"
23
+ "JEV_BROWSER_MODE": "computer"
24
24
  }
25
25
  }
26
26
  }
@@ -31,15 +31,18 @@ O formato de interpolação de variáveis varia por harness. Injete a chave por
31
31
  um secret manager ou pelo ambiente do processo; não grave a chave no arquivo
32
32
  de configuração. Para instalar no projeto Node do próprio harness:
33
33
 
34
- ```sh
35
- npm install @diegosouzacdv/jev-browser-mcp
36
- npx --yes @diegosouzacdv/jev-browser-mcp --install-browser
37
- ```
38
-
39
- No modo `harness`, a instalação baixa uma vez o Chrome/Edge que o Playwright
40
- controlará. No modo `computer`, o pacote abre o Chrome/Edge instalado e usa um
41
- perfil persistente exclusivo em `browser.computer_user_data_dir`; personalize
42
- as escolhas com `JEV_BROWSER_MODE`, `JEV_BROWSER_CHANNEL` e
34
+ ```sh
35
+ npm install @diegosouzacdv/jev-browser-mcp
36
+ ```
37
+
38
+ O padrão `computer` exige Chrome ou Edge instalado. Se optar por `harness`,
39
+ configure `JEV_BROWSER_MODE=harness` antes de instalar o navegador gerenciado
40
+ pelo Playwright com `npx --yes @diegosouzacdv/jev-browser-mcp --install-browser`.
41
+
42
+ O padrão é `computer`: o pacote abre o Chrome/Edge instalado e usa um perfil
43
+ persistente exclusivo em `browser.computer_user_data_dir`. No modo `harness`, a
44
+ instalação baixa uma vez o Chrome/Edge que o Playwright controlará. Personalize
45
+ as escolhas com `JEV_BROWSER_MODE`, `JEV_BROWSER_CHANNEL` e
43
46
  `JEV_BROWSER_PROFILE`. O browser permanece aquecido enquanto o processo MCP
44
47
  estiver ativo e fecha quando o harness encerra o processo. `JEV_PROVIDER_URL`
45
48
  e `JEV_MODEL` podem substituir os valores centrais; a credencial continua no
@@ -49,11 +52,15 @@ Uma aplicação Node também pode importar `createJevBrowserServer` por
49
52
  `@diegosouzacdv/jev-browser-mcp/server` e conectar o servidor ao transporte MCP
50
53
  que ela já utiliza.
51
54
 
52
- O pacote é montado pela raiz do repositório, mas o campo `files` do `package.json`
53
- inclui somente o código Node, a configuração compartilhada e esta documentação
54
- (além do README que o npm inclui automaticamente). Ele não publica o restante
55
- do orquestrador. A instalação por npm é a recomendada; use a referência GitHub
56
- somente quando precisar experimentar uma revisão ainda não publicada.
55
+ O pacote é montado em uma pasta temporária isolada: o publicador usa o campo
56
+ `files` do `package.json` para copiar somente o código Node, a configuração
57
+ compartilhada e esta documentação, que também vira o `README.md` da raiz do
58
+ pacote. Assim, o npm não inclui o README geral do orquestrador na página do MCP.
59
+ Para gerar e conferir o pacote antes de publicar, execute
60
+ `npm run pack:jev-browser-mcp`; para publicar uma versão já autenticada no npm, execute
61
+ `npm run publish:jev-browser-mcp`. A instalação por npm é a recomendada; use a
62
+ referência GitHub somente quando precisar experimentar uma revisão ainda não
63
+ publicada.
57
64
 
58
65
  ### Contrato do pacote
59
66
 
@@ -82,12 +89,17 @@ resultado esperado na tela.
82
89
 
83
90
  Cada plano pode usar:
84
91
 
85
- - `click`, `type`, `hover` e `select_option` com papel/nome acessível exatos;
86
- - `wait_for_text` e `wait_for_condition` (`network_idle`, limitado pelo timeout
87
- de ação configurado);
92
+ - `click`, `type`, `hover` e `select_option` com papel/nome acessível exatos;
93
+ - `timeout_seconds` opcional em cada etapa, limitado pela configuração central;
94
+ - `type` com `mode: "keys"` para digitar sequencialmente em campos com máscara,
95
+ `blur: true` para desfocar o campo e `sensitive: false` para permitir que o
96
+ valor apareça nas evidências;
97
+ - `wait_for_text` e `wait_for_condition` (`network_idle`, `hidden` ou
98
+ `text_hidden`);
88
99
  - `press_key` com `PageDown`, `PageUp`, `Home`, `End`, `ArrowDown`, `ArrowUp`,
89
100
  `Enter`, `Escape` ou `Tab`;
90
- - `assert_text`, `assert_value`, `assert_visible` e `assert_hidden`;
101
+ - `assert_text`, `assert_value`, `assert_visible` e `assert_hidden`; `assert_text`
102
+ pode receber apenas `role` quando a região, como `alert`, não tem nome acessível;
91
103
  - `upload_file` para input rotulado, botão que abre o seletor de arquivo ou
92
104
  dropzone;
93
105
  - `audit_accessibility` com axe-core para WCAG 2.1 A/AA;
@@ -235,9 +247,9 @@ do pacote Node ficam no bloco opcional `jev_browser_mcp`, que não altera o
235
247
  contrato lido pelo servidor Python. Preserve a estrutura completa exigida pelos
236
248
  validadores.
237
249
 
238
- `browser.mode` aceita `harness` ou `computer`:
250
+ `browser.mode` aceita `harness` ou `computer`; o padrão é `computer`:
239
251
 
240
- - `harness` usa Chrome headless e perfil isolado, adequado a execuções do
252
+ - `harness` usa Chrome headless e contexto isolado, adequado a execuções do
241
253
  harness e CI; o estado de autenticação é descartado ao final da chamada.
242
254
  - `computer` abre o Chrome ou Edge instalado em modo visível e usa o diretório
243
255
  persistente `browser.computer_user_data_dir`, separado por navegador. Não
@@ -246,12 +258,15 @@ validadores.
246
258
 
247
259
  `browser.max_flow_steps` limita a soma de passos declarados entre os planos e
248
260
  `browser.max_text_entry_chars` limita cada valor digitado. O resultado contém
249
- `status`, o plano escolhido, as ações executadas, a última captura acessível e
250
- se o critério esperado apareceu. Quando existem asserções explícitas, `status`
251
- também pode ser `passed` com todas elas satisfeitas; `assertions_passed` registra
252
- esse resultado e `expected_outcome_visible` continua descrevendo somente o
253
- texto global. `incomplete` significa que nenhum critério foi comprovado;
254
- confiança do Jev não substitui essa verificação.
261
+ `status`, o plano escolhido, as ações executadas, a última captura acessível e
262
+ se o critério esperado apareceu. Quando existem asserções explícitas, `status`
263
+ também pode ser `passed` com todas elas satisfeitas; `assertions_passed` registra
264
+ esse resultado e `expected_outcome_visible` continua descrevendo somente o
265
+ texto global. `incomplete` significa que nenhum critério foi comprovado;
266
+ confiança do Jev não substitui essa verificação. `timings_ms.ready_ms` mede a
267
+ espera da SPA; `warnings` registra capturas vazias durante transições; e
268
+ `failed_step` identifica índice, ação, alvo, timeout e erro resumido quando uma
269
+ etapa falha.
255
270
 
256
271
  `jev_browser_mcp.browser.max_action_timeout_seconds` limita esperas por ações,
257
272
  seletores e condições. `JEV_BROWSER_UPLOAD_ROOT` libera uploads somente dentro
@@ -267,12 +282,18 @@ Os limites de download ficam no mesmo bloco: `max_download_files`,
267
282
  limita quantas descrições de violações axe entram no resultado; a contagem total
268
283
  continua informada mesmo quando a lista é truncada.
269
284
 
270
- O `run_browser_flow` aceita `options` com `fast_path`, `snapshot_scope`,
271
- `block_trackers`, `capture_console_errors`, `capture_network_errors`,
272
- `screenshot_on_failure` e `trace_on_failure`. Sem override, os padrões são lidos
273
- de `jev_browser_mcp` em `config/ui-testing.json`: um plano candidato pula a chamada Decisions;
274
- captura de console e screenshot de falha ficam ligadas; trace, rede e bloqueio
275
- de recursos ficam desligados. `snapshot_scope` aceita `body`, `main` ou `dialog`.
285
+ O `run_browser_flow` aceita `options` com `fast_path`, `snapshot_scope`,
286
+ `block_trackers`, `capture_console_errors`, `capture_network_errors`,
287
+ `capture_network_error_bodies`, `ready_timeout_seconds`, `ready_network_idle`,
288
+ `ready_stable_ms`, `ready_text`, `reuse_page`, `screenshot_on_failure` e
289
+ `trace_on_failure`. Sem override, os padrões são lidos de `jev_browser_mcp` em
290
+ `config/ui-testing.json`: um plano candidato pula a chamada Decisions;
291
+ captura de console e screenshot de falha ficam ligadas; trace, rede e bloqueio
292
+ de recursos ficam desligados. O browser espera a SPA renderizar uma captura
293
+ acessível estável. `ready_text` pode identificar o conteúdo que marca a
294
+ prontidão. `reuse_page: true` pula a navegação somente quando a página e
295
+ `initial_url` têm a mesma origem. `snapshot_scope` aceita `body`, `main` ou
296
+ `dialog`.
276
297
 
277
298
  `block_trackers: true` bloqueia os domínios e tipos de recurso listados na
278
299
  configuração (analytics, Hotjar, fontes externas e mídia). Isso pode alterar o
@@ -280,9 +301,12 @@ layout ou o comportamento do site, então a opção é desligada por padrão.
280
301
 
281
302
  Com `capture_console_errors` e `capture_network_errors`, o retorno traz
282
303
  `console_errors` e `network_failures`, limitados em quantidade e tamanho.
283
- Respostas HTTP 4xx/5xx e requisições que falham são listadas; query strings,
284
- fragmentos, valores de formulário e nomes de arquivo são removidos ou
285
- sanitizados. Em falhas, `screenshot_on_failure` salva screenshot local e retorna
304
+ Respostas HTTP 4xx/5xx e requisições que falham são listadas; query strings,
305
+ fragmentos, valores de formulário e nomes de arquivo são removidos ou
306
+ sanitizados. `capture_network_error_bodies: true` lê somente respostas JSON
307
+ 4xx/5xx da mesma origem, limita o corpo e inclui apenas o campo textual
308
+ `message`, também sanitizado. Em falhas, `screenshot_on_failure` salva
309
+ screenshot local e retorna
286
310
  `screenshot_path`. `trace_on_failure: true` também grava um `.zip` compatível
287
311
  com o Trace Viewer do Playwright e retorna `trace_path`. O diretório padrão é
288
312
  `~/.cache/orquestrador/jev-browser-artifacts`; `JEV_BROWSER_ARTIFACT_DIR` pode
@@ -10,7 +10,7 @@ Usage:
10
10
  jev-browser-mcp --help Show this help
11
11
 
12
12
  Configuration is read from config/ui-testing.json. Set OPENROUTER_API_KEY in
13
- the environment and use JEV_BROWSER_MODE=harness or computer to choose a browser.
13
+ the environment. The default mode is computer; use JEV_BROWSER_MODE=harness to select isolated headless mode.
14
14
  For upload_file, set JEV_BROWSER_UPLOAD_ROOT to a dedicated fixture directory.
15
15
  `;
16
16
 
@@ -16,8 +16,20 @@ const CONFIG_KEYS = {
16
16
  "max_flow_steps",
17
17
  "max_text_entry_chars",
18
18
  ],
19
- nodeBrowser: [
20
- "max_action_timeout_seconds",
19
+ nodeBrowser: [
20
+ "max_action_timeout_seconds",
21
+ "ready_timeout_seconds_default",
22
+ "max_ready_timeout_seconds",
23
+ "ready_network_idle_default",
24
+ "ready_stable_ms_default",
25
+ "max_ready_stable_ms",
26
+ "post_step_ready_timeout_seconds_default",
27
+ "max_step_timeout_seconds",
28
+ "key_delay_ms_default",
29
+ "reuse_page_default",
30
+ "capture_network_error_bodies_default",
31
+ "max_network_error_body_bytes",
32
+ "max_network_error_message_chars",
21
33
  "max_upload_files",
22
34
  "max_upload_path_chars",
23
35
  "max_upload_file_bytes",
@@ -206,7 +218,7 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
206
218
  const uploadRoot = configuredUploadRoot
207
219
  ? configuredDirectory(configuredUploadRoot, `environment ${uploadRootEnv}`)
208
220
  : null;
209
- const snapshotScope = browserOptions.snapshot_scope_default;
221
+ const snapshotScope = browserOptions.snapshot_scope_default;
210
222
  if (!new Set(["body", "main", "dialog"]).has(snapshotScope)) {
211
223
  throw new JevBrowserError("config/ui-testing.json jev_browser_mcp.browser.snapshot_scope_default must be body, main, or dialog");
212
224
  }
@@ -215,11 +227,28 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
215
227
  "jev_browser_mcp.browser.tracker_host_suffixes",
216
228
  (host) => /^[a-z0-9.-]+$/i.test(host) && !host.startsWith(".") && !host.endsWith("."),
217
229
  );
218
- const blockedResourceTypes = stringList(
230
+ const blockedResourceTypes = stringList(
219
231
  browserOptions.blocked_resource_types,
220
232
  "jev_browser_mcp.browser.blocked_resource_types",
221
233
  (type) => new Set(["font", "media", "image"]).has(type),
222
- );
234
+ );
235
+ const maxReadyTimeoutSeconds = positiveNumber(browserOptions.max_ready_timeout_seconds, "jev_browser_mcp.browser.max_ready_timeout_seconds");
236
+ const readyTimeoutSecondsDefault = positiveNumber(browserOptions.ready_timeout_seconds_default, "jev_browser_mcp.browser.ready_timeout_seconds_default");
237
+ const maxReadyStableMs = positiveNumber(browserOptions.max_ready_stable_ms, "jev_browser_mcp.browser.max_ready_stable_ms", true);
238
+ const readyStableMsDefault = positiveNumber(browserOptions.ready_stable_ms_default, "jev_browser_mcp.browser.ready_stable_ms_default", true);
239
+ if (readyTimeoutSecondsDefault > maxReadyTimeoutSeconds) {
240
+ throw new JevBrowserError("config/ui-testing.json ready timeout default exceeds its configured maximum");
241
+ }
242
+ if (readyStableMsDefault > maxReadyStableMs) {
243
+ throw new JevBrowserError("config/ui-testing.json stable snapshot default exceeds its configured maximum");
244
+ }
245
+ const postStepReadyTimeoutSeconds = positiveNumber(
246
+ browserOptions.post_step_ready_timeout_seconds_default,
247
+ "jev_browser_mcp.browser.post_step_ready_timeout_seconds_default",
248
+ );
249
+ if (postStepReadyTimeoutSeconds > maxReadyTimeoutSeconds) {
250
+ throw new JevBrowserError("config/ui-testing.json post-step readiness timeout exceeds its configured maximum");
251
+ }
223
252
 
224
253
  return Object.freeze({
225
254
  env,
@@ -229,7 +258,14 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
229
258
  profileDir: profileDirectory(profileTemplate, channel, env.JEV_BROWSER_PROFILE),
230
259
  maxFlowSteps: positiveNumber(document.browser.max_flow_steps, "browser.max_flow_steps", true),
231
260
  maxTextEntryChars: positiveNumber(document.browser.max_text_entry_chars, "browser.max_text_entry_chars", true),
232
- actionTimeoutMs: positiveNumber(browserOptions.max_action_timeout_seconds, "jev_browser_mcp.browser.max_action_timeout_seconds") * 1000,
261
+ actionTimeoutMs: positiveNumber(browserOptions.max_action_timeout_seconds, "jev_browser_mcp.browser.max_action_timeout_seconds") * 1000,
262
+ maxReadyTimeoutSeconds,
263
+ maxReadyStableMs,
264
+ postStepReadyTimeoutMs: postStepReadyTimeoutSeconds * 1000,
265
+ maxStepTimeoutSeconds: positiveNumber(browserOptions.max_step_timeout_seconds, "jev_browser_mcp.browser.max_step_timeout_seconds"),
266
+ keyDelayMs: positiveNumber(browserOptions.key_delay_ms_default, "jev_browser_mcp.browser.key_delay_ms_default", true),
267
+ maxNetworkErrorBodyBytes: positiveNumber(browserOptions.max_network_error_body_bytes, "jev_browser_mcp.browser.max_network_error_body_bytes", true),
268
+ maxNetworkErrorMessageChars: positiveNumber(browserOptions.max_network_error_message_chars, "jev_browser_mcp.browser.max_network_error_message_chars", true),
233
269
  maxUploadFiles: positiveNumber(browserOptions.max_upload_files, "jev_browser_mcp.browser.max_upload_files", true),
234
270
  maxUploadPathChars: positiveNumber(browserOptions.max_upload_path_chars, "jev_browser_mcp.browser.max_upload_path_chars", true),
235
271
  maxUploadFileBytes: positiveNumber(browserOptions.max_upload_file_bytes, "jev_browser_mcp.browser.max_upload_file_bytes", true),
@@ -246,8 +282,13 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
246
282
  screenshotOnFailure: booleanValue(browserOptions.screenshot_on_failure_default, "jev_browser_mcp.browser.screenshot_on_failure_default"),
247
283
  traceOnFailure: booleanValue(browserOptions.trace_on_failure_default, "jev_browser_mcp.browser.trace_on_failure_default"),
248
284
  captureConsoleErrors: booleanValue(browserOptions.capture_console_errors_default, "jev_browser_mcp.browser.capture_console_errors_default"),
249
- captureNetworkErrors: booleanValue(browserOptions.capture_network_errors_default, "jev_browser_mcp.browser.capture_network_errors_default"),
250
- blockTrackers: booleanValue(browserOptions.block_trackers_default, "jev_browser_mcp.browser.block_trackers_default"),
285
+ captureNetworkErrors: booleanValue(browserOptions.capture_network_errors_default, "jev_browser_mcp.browser.capture_network_errors_default"),
286
+ captureNetworkErrorBodies: booleanValue(browserOptions.capture_network_error_bodies_default, "jev_browser_mcp.browser.capture_network_error_bodies_default"),
287
+ blockTrackers: booleanValue(browserOptions.block_trackers_default, "jev_browser_mcp.browser.block_trackers_default"),
288
+ readyNetworkIdle: booleanValue(browserOptions.ready_network_idle_default, "jev_browser_mcp.browser.ready_network_idle_default"),
289
+ reusePage: booleanValue(browserOptions.reuse_page_default, "jev_browser_mcp.browser.reuse_page_default"),
290
+ readyTimeoutSeconds: readyTimeoutSecondsDefault,
291
+ readyStableMs: readyStableMsDefault,
251
292
  snapshotScope,
252
293
  }),
253
294
  trackerHostSuffixes: Object.freeze(trackerHostSuffixes),