@diegosouzacdv/jev-browser-mcp 0.7.0 → 0.7.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.
@@ -17,7 +17,7 @@ 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.7.0"],
20
+ "args": ["--yes", "@diegosouzacdv/jev-browser-mcp@0.7.2"],
21
21
  "env": {
22
22
  "OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
23
23
  "JEV_BROWSER_MODE": "computer"
@@ -32,7 +32,7 @@ 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
34
  ```sh
35
- npm install @diegosouzacdv/jev-browser-mcp@0.7.0
35
+ npm install @diegosouzacdv/jev-browser-mcp@0.7.2
36
36
  ```
37
37
 
38
38
  Se o harness executa o MCP repetidamente nesta máquina, instale uma versão
@@ -40,7 +40,7 @@ fixa globalmente para evitar a resolução e o download feitos pelo `npx` em cad
40
40
  inicialização:
41
41
 
42
42
  ```sh
43
- npm install --global @diegosouzacdv/jev-browser-mcp@0.7.0
43
+ npm install --global @diegosouzacdv/jev-browser-mcp@0.7.2
44
44
  ```
45
45
 
46
46
  Depois configure o servidor MCP com `command: "jev-browser-mcp"` e `args: []`.
@@ -48,8 +48,8 @@ Para atualizar, rode `npm install --global
48
48
  @diegosouzacdv/jev-browser-mcp@<versão>` e reinicie o processo do harness.
49
49
 
50
50
  O padrão `computer` exige Chrome ou Edge instalado. Se optar por `harness`,
51
- configure `JEV_BROWSER_MODE=harness` antes de instalar o navegador gerenciado
52
- pelo Playwright com `npx --yes @diegosouzacdv/jev-browser-mcp@0.7.0 --install-browser`.
51
+ configure `JEV_BROWSER_MODE=harness` antes de instalar o navegador gerenciado
52
+ pelo Playwright com `npx --yes @diegosouzacdv/jev-browser-mcp@0.7.2 --install-browser`.
53
53
 
54
54
  O padrão é `computer`: o pacote abre o Chrome/Edge instalado e usa um perfil
55
55
  persistente exclusivo por processo MCP em `browser.computer_user_data_dir`. O
@@ -85,22 +85,49 @@ publicada.
85
85
 
86
86
  ### Contrato do pacote
87
87
 
88
- O executável oferece `browser_health`, `describe_actions`, `choose_next_action`
89
- e `run_browser_flow`, mantém uma sessão do browser por processo e reutiliza
90
- essa sessão entre chamadas. `browser_health` informa versão, sessão e
88
+ O executável oferece `browser_health`, `describe_actions`, `choose_next_action`,
89
+ `run_browser_flow`, `run_test_suite` e `get_browser_flow_result`, mantém uma sessão do browser
90
+ por processo e reutiliza essa sessão entre chamadas. `browser_health` informa versão, sessão e
91
91
  capacidades; `describe_actions` publica o schema atual das ações, opções,
92
- aliases e placeholders. Consulte essas ferramentas antes de construir um plano
93
- para evitar nomes de campos desatualizados. O plano
94
- passado ao Jev continua declarativo: clique, preenchimento, seleção nativa,
95
- hover, espera, teclas aprovadas, asserções por elemento, upload restrito a uma
96
- raiz local configurada e reações idempotentes a um comentário único. Também
97
- aceita um nome de iframe para as ações que ocorrem dentro dele. Não aceita
98
- JavaScript enviado pelo harness nem coordenadas. Além de papel/nome acessível,
92
+ aliases, placeholders, campos obrigatórios de `flow`/plano e requisitos
93
+ condicionais por ação. `run_browser_flow` agrega campos obrigatórios ausentes
94
+ em todos os planos e etapas antes de abrir o navegador. Consulte essas ferramentas
95
+ antes de construir um plano para evitar nomes de campos desatualizados. O plano
96
+ passado ao Jev continua declarativo: clique, preenchimento, seleção nativa,
97
+ hover, espera, teclas aprovadas, asserções por elemento, upload restrito ao
98
+ staging da sessão ou a uma raiz local configurada e reações idempotentes a um
99
+ comentário único. Também
100
+ aceita um nome de iframe para as ações que ocorrem dentro dele. Não aceita
101
+ JavaScript enviado pelo harness nem coordenadas. Além de papel/nome acessível,
99
102
  aceita `label`, `placeholder`, `title`, `text`, `test_id` e `selector`; CSS/XPath
100
103
  são o último recurso e geram um aviso no resultado. Ele usa a
101
- biblioteca Playwright diretamente, sem iniciar um segundo servidor MCP do
102
- Playwright. O transporte MCP usa `stdio`; toda saída de diagnóstico vai para
103
- `stderr` para não misturar com JSON-RPC.
104
+ biblioteca Playwright diretamente, sem iniciar um segundo servidor MCP do
105
+ Playwright. O transporte MCP usa `stdio`; toda saída de diagnóstico vai para
106
+ `stderr` para não misturar com JSON-RPC.
107
+
108
+ ### Validar o MCP com Chromium e uma aplicação sintética
109
+
110
+ O teste ponta a ponta do pacote inicia o servidor MCP real por `stdio`, abre o
111
+ Chromium controlado pelo Playwright e exercita uma aplicação HTTP local
112
+ descartável. Ele cobre descoberta das ferramentas, busca com resposta 200,
113
+ erro 503 com corpo, upload de fixture Unicode seguido de download e comparação
114
+ de conteúdo, além de execução em segundo plano e relatório de suíte. Não acessa
115
+ SAFI, redes externas, bancos ou serviços remotos.
116
+
117
+ No checkout do repositório, instale as dependências e o navegador uma vez e
118
+ execute somente esse cenário:
119
+
120
+ ```sh
121
+ npm ci
122
+ node mcp_servers/jev-browser-npm/bin/jev-browser-mcp.cjs --install-browser
123
+ node --test mcp_servers/jev-browser-npm/tests/stdio.test.mjs
124
+ ```
125
+
126
+ O teste cria e remove sua aplicação e seus arquivos temporários. Para validar
127
+ Oracle, PostgreSQL ou servidores FTP/FTPS/SFTP, configure serviços de teste
128
+ isolados e credenciais pelo ambiente e use os perfis explicitamente descritos
129
+ em “Perfis e integrações de teste”; o cenário sintético não representa essas
130
+ integrações reais.
104
131
 
105
132
  O modo `computer` grava cookies no perfil exclusivo configurado, e conteúdo da
106
133
  página pode conter instruções maliciosas. O Jev recebe a captura acessível com
@@ -174,18 +201,39 @@ Cada plano pode usar:
174
201
  - `assert_network` valida resposta HTTP observada (inclusive sucesso 2xx ou erro
175
202
  esperado); `wait_for_request` aguarda uma resposta usando `*` como curinga na
176
203
  URL e filtros opcionais de método/status; `assert_ws` confere texto, pares
177
- JSON esperados ou código de fechamento de WebSocket;
204
+ JSON esperados ou código de fechamento de WebSocket. `wait_for_request`
205
+ consulta o histórico desde o começo da chamada, inclusive requisições feitas
206
+ durante a navegação inicial e dentro de iframes; URLs cobertas por uma
207
+ asserção ficam retidas mesmo quando a SPA excede o buffer circular;
178
208
  - `evaluate` lê somente caminhos de propriedades como `navigator.mediaDevices`,
179
209
  `document.readyState` ou `location.pathname`. Não aceita código, chamadas de
180
210
  função, armazenamento local ou propriedades de credenciais. Strings e números
181
211
  voltam diretamente; objetos e arrays retornam somente tipo, chaves e tamanho,
182
212
  sem despejar seus valores;
213
+ - `http_request` executa GET/HEAD e, com perfil DEV/HML e autorização explícita,
214
+ também POST/PUT/PATCH/DELETE com a sessão da página; aceita JSON ou multipart,
215
+ limita a resposta e pode extrair campos escalares para variáveis do fluxo.
216
+ `inspect_cookies`
217
+ retorna apenas metadados e valores redigidos; `read_angular_state` devolve o
218
+ nome do estado e nomes dos parâmetros, sem valores. Inspeções continuam
219
+ exigindo `local_only: true` e página em loopback. `http_request` exige
220
+ `local_only` ou um perfil, fica restrito à mesma origem, não segue
221
+ redirecionamentos e não inclui o corpo sem `include_body: true`;
222
+ - `frame: "first"` seleciona o primeiro iframe; `frame: {"url_contains":"..."}`
223
+ localiza um iframe pela URL. Uma string diferente continua buscando o nome ou
224
+ título exato do frame;
225
+ - `navigate_menu` aceita `path` para menus acessíveis ou `screen` para procurar
226
+ código/nome pela tela Acesso Rápido do SAFI. O segundo caminho usa a interface
227
+ do SAFI e preserva as verificações de acesso do próprio aplicativo;
183
228
  - `new_tab`, `switch_tab` e `new_context` abrem/selecionam abas e criam contexto
184
229
  separado dentro do fluxo; `target_page: "popup"` mantém popups abertos para
185
230
  passos posteriores;
186
231
  - `if_visible` em um clique torna a ação condicional e
187
232
  `when: {"visible":"..."}` em cada plano limita as opções do Jev às que
188
233
  correspondem à tela atual;
234
+ - `near: {"text":"..."}` com `role` e `name` procura o controle com esse nome
235
+ entre os elementos posteriores à âncora; o nome pedido não é descartado nem
236
+ substituído pelo primeiro botão/link seguinte;
189
237
  - `fill` é alias de `type`, `press_key` de `press`, `duration_ms` de `wait.ms` e
190
238
  `value` de campos de texto. `timeout_ms` funciona por etapa. Placeholders usam
191
239
  `{nome}`; referências desconhecidas e `{{nome}}` são recusadas antes da
@@ -199,11 +247,18 @@ Cada plano pode usar:
199
247
  - `clear_storage` limpa cookies, local/session storage, IndexedDB, Cache Storage
200
248
  e service workers do contexto atual. Como opção de fluxo,
201
249
  `options.clear_storage: true` também recarrega a página antes do snapshot inicial;
202
- - `assert_text`, `assert_value`, `assert_visible`, `assert_hidden` e
203
- `assert_enabled`; `assert_text`
204
- pode receber apenas `role` quando a região, como `alert`, não tem nome acessível;
250
+ - `assert_text`, `assert_value`, `assert_attribute`, `assert_visible`,
251
+ `assert_hidden` e `assert_enabled`; `assert_attribute` compara um atributo
252
+ por igualdade ou substring e não inclui o valor observado no resultado;
253
+ `assert_text` pode receber apenas `role` quando a região, como `alert`, não tem
254
+ nome acessível;
205
255
  - `upload_file` para input rotulado, botão que abre o seletor de arquivo ou
206
256
  dropzone;
257
+ - `generate_fixture`, `assert_download`, `assert_db`, `assert_log`,
258
+ `start_email_capture`, `assert_email`, `assert_remote_file`,
259
+ `delete_remote_file` e `run_hook` cobrem dados e serviços externos de teste;
260
+ os limites, pré-requisitos e regras por ambiente estão na seção
261
+ [Perfis e integrações de teste](#perfis-e-integrações-de-teste);
207
262
  - `target_page: "popup"` para continuar no popup aberto com `expect_popup: true`;
208
263
  locators semânticos também atravessam open Shadow DOM. Shadow DOM fechado não
209
264
  é acessível ao Playwright;
@@ -222,7 +277,10 @@ As proteções e evidências por etapa usam estes campos:
222
277
  - `confirm_modal` combina o clique no gatilho, a conferência do texto e o clique
223
278
  afirmativo em uma única etapa protegida. Também aceita os aliases
224
279
  `trigger`/`confirm`, por exemplo
225
- `{"action":"confirm_modal","trigger":"Salvar","expected_text":"Confirma a alteração?","confirm":"OK"}`;
280
+ `{"action":"confirm_modal","trigger":"Salvar","trigger_match":"contains","expected_text":"Confirma a alteração?","confirm":"OK"}`.
281
+ O nome do gatilho é exato por padrão; `trigger_match` também aceita `exact` e
282
+ `regex`. O texto esperado é conferido antes de clicar no botão afirmativo. O
283
+ clique no gatilho ainda respeita a autorização de mutação;
226
284
  - `mutating: true` marca etapas que gravam dados ou disparam efeitos externos.
227
285
  O MCP também classifica botões conhecidos como `Salvar`, `Emitir`, `Cancelar`
228
286
  ou `Sim` pelo nome e pelo controle resolvido, inclusive quando o plano usa um
@@ -250,10 +308,19 @@ booleanos. Use `{pedido}` em `flow`, `expected_outcome` e nos campos textuais do
250
308
  plano para reutilizar um valor sem editar o roteiro em vários lugares. A ação
251
309
  `extract` lê `text` (padrão), `value` ou `attribute` de um elemento e salva o
252
310
  resultado na variável indicada por `as`; etapas seguintes podem usar
253
- `{documento}`. O valor extraído é tratado como sensível e fica oculto no
254
- resultado por padrão; use `sensitive: false` só quando for apropriado exibi-lo.
255
- `resolved_target.match_strategy` informa quando um rótulo foi
256
- associado por proximidade (`label-proximity`) em vez de um `label[for]` direto.
311
+ `{documento}`. O valor extraído é tratado como sensível e fica oculto no
312
+ resultado por padrão; use `sensitive: false` só quando for apropriado exibi-lo.
313
+ Valores que correspondem a segredos já detectados continuam redigidos. Para
314
+ provar que um atributo corresponde a um valor sem devolvê-lo ao harness, use
315
+ `assert_attribute` com `match: "exact"` (padrão) ou `match: "contains"`.
316
+ Por exemplo, para validar o caminho de um documento em um iframe:
317
+
318
+ ```json
319
+ {"action":"assert_attribute","selector":"iframe#documento","attribute":"src","expected":"/documento.pdf","match":"contains"}
320
+ ```
321
+
322
+ `resolved_target.match_strategy` informa quando um rótulo foi
323
+ associado por proximidade (`label-proximity`) em vez de um `label[for]` direto.
257
324
 
258
325
  A ação `assert_network` verifica respostas observadas depois da etapa anterior,
259
326
  incluindo respostas 2xx ou erros esperados como 404. Exemplo:
@@ -271,11 +338,26 @@ incluindo respostas 2xx ou erros esperados como 404. Exemplo:
271
338
  Essa asserção aparece na evidência como `response_status`, separado do campo
272
339
  `status` da etapa (`passed` ou `failed`). `message_contains` lê somente resposta
273
340
  da mesma origem e respeita o limite configurado para captura de corpos.
274
- `assert_network`, `wait_for_request` e `expected_outcome.request` consultam o
275
- histórico limitado de respostas depois do marcador da etapa ou do início do
276
- fluxo. O limite vem de `jev_browser_mcp.browser.max_network_history_events` e
277
- está em 4096 eventos nesta configuração; assim, chamadas anteriores ao marcador
278
- não satisfazem a asserção.
341
+ `assert_network` consulta respostas desde o marcador da etapa e usa a resposta
342
+ da navegação inicial como fallback quando nenhuma resposta posterior corresponde.
343
+ `wait_for_request` e `expected_outcome.request` consultam desde o início do fluxo.
344
+ O MCP arma observadores de `wait_for_request` antes dos passos quando o padrão
345
+ pode ser resolvido; se depender de variável extraída, arma assim que a variável
346
+ estiver disponível. A observação retém a resposta mesmo que ela termine durante
347
+ um clique ou que tráfego posterior ultrapasse o buffer circular. `assert_network`
348
+ mantém respostas correspondentes no histórico limitado por
349
+ `jev_browser_mcp.browser.max_network_history_events`. A captura de página também
350
+ observa requisições de iframes.
351
+ Um único plano `fast_path` composto apenas por `assert_network` e
352
+ `wait_for_request` não espera um snapshot visual, então endpoints que respondem
353
+ sem HTML também podem ser verificados. `navigation_http_status` registra um
354
+ status HTTP de erro recebido pela navegação inicial; ele não é uma falha de rede.
355
+
356
+ `options.capture_network: "all"` inclui até o limite de diagnóstico eventos
357
+ com URL sem query string, método, status, tipo de recurso, duração e cabeçalhos
358
+ de resposta de uma allowlist. `capture_network_url_contains` filtra as URLs.
359
+ Quando o Chromium informa cookies bloqueados, `blocked_cookies` mostra nome,
360
+ domínio, caminho, flags e motivo, sem revelar valores.
279
361
 
280
362
  Para aguardar a API antes de abrir a interface, passe
281
363
  `options.wait_for_http: {"url":"http://localhost:8000/health","timeout_ms":30000}`.
@@ -312,10 +394,38 @@ controle próximo ao texto visível do rótulo.
312
394
  `15287210`; `row_containing_exact` continua disponível para texto de célula
313
395
  exato. `check` e `uncheck` alteram checkboxes, e `select_option` aceita rótulo
314
396
  exato (`option`), valor (`value`) ou rótulo parcial único (`label_contains`).
315
- `navigate_menu` percorre uma lista de rótulos exatos e abre `#open_btn` quando o
316
- primeiro item ainda está oculto. Se vários itens visíveis tiverem o mesmo
317
- rótulo, seleciona a última ocorrência, útil quando o submenu recém-aberto
318
- repete o nome da categoria pai.
397
+ `navigate_menu` percorre uma lista de rótulos exatos e abre `#open_btn` quando o
398
+ primeiro item ainda está oculto. Se vários itens visíveis tiverem o mesmo
399
+ rótulo, seleciona a última ocorrência, útil quando o submenu recém-aberto
400
+ repete o nome da categoria pai. Para o SAFI, `screen` pesquisa o nome ou código
401
+ na tela Acesso Rápido, exige um resultado único e clica no link oficial da linha;
402
+ por exemplo, `{"action":"navigate_menu","screen":"TCN00022"}`.
403
+
404
+ Em uma instância local do SAFI, `http_request` pode obter a lista de telas pela
405
+ sessão já aberta sem ler o HTML:
406
+
407
+ ```json
408
+ {
409
+ "flow": "Consultar telas disponíveis no perfil atual do SAFI",
410
+ "initial_url": "http://localhost:8011/safi/",
411
+ "candidate_plans": {
412
+ "inspect": {
413
+ "description": "Ler a lista de telas do SAFI no ambiente local",
414
+ "steps": [{
415
+ "action": "http_request",
416
+ "url": "/safi/api/menu/telas_menu",
417
+ "include_body": true
418
+ }]
419
+ }
420
+ },
421
+ "options": { "local_only": true, "fast_path": true }
422
+ }
423
+ ```
424
+
425
+ O endpoint usa a sessão atual e pode retornar uma lista específica do perfil.
426
+ Os valores de cookies nunca são retornados. O acesso ao menu continua sujeito
427
+ às permissões verificadas pela própria interface; não use esse diagnóstico
428
+ contra produção.
319
429
 
320
430
  ```json
321
431
  {
@@ -423,21 +533,173 @@ asserções são aceitos. `comment` também é aceito em planos e passos, mas é
423
533
  ignorado e não é enviado ao Jev. Campos fora do contrato continuam sendo
424
534
  recusados.
425
535
 
426
- ### Upload de arquivos
427
-
428
- Defina `JEV_BROWSER_UPLOAD_ROOT` como uma pasta absoluta que contenha os arquivos
429
- de fixture. Cada caminho passado ao fluxo também precisa ser absoluto; o MCP
430
- resolve links simbólicos e recusa qualquer arquivo fora dessa raiz. Só aceita
431
- arquivos regulares, até 5 arquivos por passo, 10 MiB por arquivo e 25 MiB no
432
- total do passo, conforme `jev_browser_mcp.browser` em `config/ui-testing.json`.
433
- Os bytes são lidos e validados no processo local antes de serem entregues ao
434
- Playwright. O MCP não envia esses bytes ao Jev nem os inclui diretamente na
435
- resposta; texto que o próprio site exibir na interface ainda pode aparecer no
436
- snapshot devolvido ao harness.
437
-
438
- ```json
439
- {"action":"upload_file","target":"input","label":"Nota fiscal","file_path":"/fixtures/nota.pdf"}
440
- ```
536
+ ### Upload de arquivos
537
+
538
+ Por padrão, `browser_health` cria e devolve `upload_staging_directory` dentro do
539
+ projeto local: `<JEV_BROWSER_PROJECT_ROOT ou diretório de trabalho do MCP>/.jev-browser/tmp/uploads/mcp-<pid>-<sessão>`.
540
+ O harness deve copiar para essa pasta somente os arquivos de fixture que o
541
+ fluxo precisa, e usar o caminho absoluto retornado por `browser_health`. Se o
542
+ processo MCP iniciar com outro diretório de trabalho, defina
543
+ `JEV_BROWSER_PROJECT_ROOT` como o caminho absoluto do projeto. Em um MCP remoto,
544
+ esse caminho pertence ao host do servidor; o harness precisa conseguir gravar
545
+ no mesmo host ou volume.
546
+
547
+ Cada caminho passado ao fluxo precisa ser absoluto; o MCP resolve links
548
+ simbólicos e recusa qualquer arquivo fora do staging privado daquela sessão e
549
+ da raiz explícita configurada, se houver.
550
+ Só aceita arquivos regulares, até 5 arquivos por passo, 10 MiB por arquivo e
551
+ 25 MiB no total do passo, conforme `jev_browser_mcp.browser` em
552
+ `config/ui-testing.json`. O operador pode ajustar os limites no arquivo ou com
553
+ `JEV_BROWSER_MAX_UPLOAD_FILE_BYTES` e
554
+ `JEV_BROWSER_MAX_UPLOAD_TOTAL_BYTES`; o teto total precisa ser pelo menos o
555
+ limite por arquivo. `browser_health` e `describe_actions` mostram os valores
556
+ ativos. Esse teto pertence ao MCP e ao navegador local; alterá-lo não aumenta
557
+ o limite de arquivo aceito pelo provedor Jev. Os bytes são lidos e validados no
558
+ processo local antes de serem entregues ao Playwright. O MCP não envia esses
559
+ bytes ao Jev nem os inclui diretamente na resposta; texto que o próprio site
560
+ exibir na interface ainda pode aparecer no snapshot devolvido ao harness.
561
+
562
+ Depois de um fluxo que usa `upload_file`, `generate_fixture` ou multipart com
563
+ arquivos, o MCP remove todos os arquivos e subpastas da pasta exclusiva da
564
+ sessão, inclusive quando o fluxo falha. Arquivos fornecidos pela raiz
565
+ `JEV_BROWSER_UPLOAD_ROOT` não são apagados. Um
566
+ `dry_run` ou `confirmation_required` preserva os arquivos para que a próxima
567
+ chamada confirmada possa usá-los; o encerramento normal do servidor também
568
+ limpa essa pasta. O diretório e os diretórios pais permanecem, prontos para uma
569
+ próxima chamada. O original fora da pasta de staging não é tocado.
570
+
571
+ `JEV_BROWSER_UPLOAD_ROOT` continua disponível como uma raiz explícita adicional;
572
+ o MCP aceita arquivos tanto dela quanto do staging privado do projeto e nunca
573
+ apaga arquivos da raiz explícita. A pasta privada gerenciada no projeto continua
574
+ disponível para fixtures geradas pelo MCP e multipart; só o staging gerenciado
575
+ é limpo automaticamente. Não aponte para o disco inteiro ou para a pasta home.
576
+
577
+ Fixtures `generate_fixture` aceitam `kind: "pdf"`, `"text"` ou `"bytes"`,
578
+ `as`, `file_name` (inclusive Unicode), `content` para PDF/texto e `size_bytes`
579
+ para bytes determinísticos. São criadas em
580
+ `.jev-browser/tmp/uploads/` dentro do projeto e expostas como variável para
581
+ passos seguintes. O tamanho máximo é o menor entre
582
+ `max_upload_file_bytes` configurado e **47 MiB**, limite atual do provedor Jev
583
+ para um arquivo. `assert_download` compara SHA-256, tamanho e tipo MIME do
584
+ último download com o fixture informado. O original do fixture continua sendo
585
+ temporário e é limpo no teardown do fluxo ou da suíte.
586
+
587
+ ### Perfis e integrações de teste
588
+
589
+ O MCP lê perfis opcionais de `.jev-browser/profiles.json` no projeto. Para outro
590
+ arquivo dentro do projeto, defina `JEV_BROWSER_PROFILES_FILE` com caminho
591
+ absoluto. O arquivo é JSON versão 1 e nunca deve conter senhas ou tokens:
592
+ campos como `connection_env`, `password_env` e `username_env` guardam somente o
593
+ nome da variável de ambiente, e os valores são lidos pelo processo MCP.
594
+
595
+ Exemplo mínimo de perfil DEV:
596
+
597
+ ```json
598
+ {
599
+ "version": 1,
600
+ "profiles": {
601
+ "dev": {
602
+ "environment": "dev",
603
+ "application_origins": ["http://localhost:8000"],
604
+ "database": {
605
+ "engine": "postgresql",
606
+ "connection_env": "JEV_TEST_DATABASE_URL",
607
+ "timeout_ms": 5000,
608
+ "max_rows": 100
609
+ },
610
+ "log_sources": {"api": "logs/test-api.log"},
611
+ "smtp_capture": {"port": 0, "bind_address": "127.0.0.1", "max_messages": 20},
612
+ "remotes": {},
613
+ "hooks": {}
614
+ }
615
+ }
616
+ }
617
+ ```
618
+
619
+ `database.engine` suporta `postgresql` e `oracle`. PostgreSQL usa
620
+ `connection_env`; Oracle usa `connect_env`, `user_env` e `password_env`. Ambos
621
+ exigem `timeout_ms` e `max_rows`. `assert_db` aceita apenas um `SELECT` sem
622
+ comandos adicionais, usa binds, transação somente leitura, timeout e teto de
623
+ linhas do perfil. Configure também as credenciais para uma identidade de banco
624
+ com privilégios somente de leitura; a transação read-only do MCP complementa
625
+ essa separação. Essa ação é somente leitura em DEV, HML e PRD.
626
+
627
+ `application_origins` é a allowlist de origens HTTP. `http_request` usa a sessão
628
+ da página, aceita `GET`, `HEAD`, `POST`, `PUT`, `PATCH` e `DELETE`, JSON ou
629
+ multipart. POST/PUT/PATCH/DELETE exigem `options.allow_mutations: true` e um
630
+ perfil DEV/HML cuja allowlist inclua a origem. PRD recusa mutações. HML só
631
+ permite escrita na aplicação com essa autorização explícita. Para uma página
632
+ local sem perfil, `local_only: true` só libera GET/HEAD em loopback.
633
+ `extract` usa caminhos JSON e só expõe valores escalares; campos com nomes
634
+ sensíveis entram na redação automática.
635
+
636
+ `log_sources` define nomes e caminhos de arquivos que o MCP pode consultar.
637
+ `assert_log` lê apenas essas origens e pode restringir o trecho ao que foi
638
+ escrito desde o início do fluxo ou desde `step:N`; logs são sempre somente
639
+ leitura. Configure caminhos absolutos apenas quando a política local exigir;
640
+ prefira caminhos relativos ao projeto.
641
+
642
+ `smtp_capture` sobe um servidor de captura limitado a `127.0.0.1` ou `::1`;
643
+ `port: 0` escolhe uma porta livre. Use `start_email_capture` antes de acionar o
644
+ envio na aplicação e depois `assert_email` com destinatário, assunto ou corpo.
645
+ O servidor guarda as mensagens somente em memória durante a chamada/suíte e as
646
+ remove no teardown. A resposta e o relatório registram a asserção e horário,
647
+ sem incluir assunto ou corpo.
648
+
649
+ `remotes` permite verificar arquivos em FTP, FTPS ou SFTP, limitado a um
650
+ `root` remoto configurado. Credenciais vêm de `username_env` e `password_env`;
651
+ SFTP também exige `host_fingerprint_env` para validar a chave do host.
652
+ Configure cada usuário remoto em uma pasta isolada/chroot compatível com o
653
+ `root` allowlisted.
654
+ `assert_remote_file` verifica existência e, opcionalmente, tamanho e SHA-256.
655
+ `delete_remote_file` é destrutivo: só funciona em perfil DEV com
656
+ `allow_mutations: true`. Banco e logs permanecem somente leitura em todos os
657
+ ambientes.
658
+
659
+ `hooks` são scripts Node nomeados e allowlisted, com caminho dentro do projeto,
660
+ argumentos declarados (`string`, `integer` ou `boolean`), comprimento máximo e
661
+ `timeout_ms` obrigatório. Não há shell nem comando arbitrário; hooks só podem
662
+ ser definidos/executados em DEV e precisam de `allow_mutations: true`. PRD
663
+ recusa hooks e qualquer etapa marcada `mutating: true`.
664
+
665
+ ### Suítes JSON e relatórios
666
+
667
+ Guarde uma definição por PBI em `.jev-browser/suites/<id>.json`. Ela possui
668
+ `version: 1`, `id`, `criteria` e opcionalmente `description`, `params`,
669
+ `environment_profile`, `flows`, `setup` e `teardown`. Cada fluxo reutilizável
670
+ tem `flow`, `candidate_plans` e opcionalmente `initial_url`, `expected_outcome`,
671
+ `params` e `options`; cada critério tem `id`, `description` e `flow`, que pode
672
+ referenciar um fluxo pelo nome ou declará-lo inline. Placeholders usam
673
+ `{nome}` e recebem prioridade dos parâmetros do pedido sobre os parâmetros do
674
+ arquivo.
675
+
676
+ Antes de abrir o navegador, `run_test_suite` valida todo o JSON, caminhos de
677
+ relatório, placeholders, limites, perfil e todas as etapas de setup, critérios
678
+ e teardown. Executa sequencialmente por padrão e tenta teardown mesmo se setup
679
+ ou critério falhar. A suíte mantém a sessão do browser e a captura de integrações
680
+ durante suas etapas, depois limpa os arquivos temporários. Um relatório
681
+ Markdown é salvo por padrão em `.jev-browser/reports/`; `report_path` aceita um
682
+ caminho relativo dentro do projeto. O relatório inclui status por critério,
683
+ passos, duração, localizadores, evidências de rede, banco, logs, e-mail, arquivos,
684
+ screenshots e limpeza. URLs de evidência perdem query, fragmento e segmentos que
685
+ parecem IDs; valores de campos e corpos de mensagens não são incluídos. Valores
686
+ secretos conhecidos por variáveis de ambiente e parâmetros sensíveis são
687
+ redigidos. O tamanho do arquivo é limitado por
688
+ `max_test_suite_report_bytes`; se o relatório atingir o teto, o resultado
689
+ informa `report_truncated: true` e o próprio Markdown marca o truncamento.
690
+
691
+ Para permitir polling quando a chamada for longa, use `background: true` e
692
+ consulte o `run_id` com `get_browser_flow_result`. A definição da suíte precisa
693
+ ser criada no projeto do processo MCP antes da chamada. `describe_actions`
694
+ publica o formato da suíte, ferramentas, opções, ações, campos condicionais e
695
+ limites atualmente configurados; `browser_health` informa versões e capacidades.
696
+
697
+ Consulte `browser_health` para descobrir a pasta da sessão e, então, passe o
698
+ caminho absoluto do arquivo preparado pelo harness:
699
+
700
+ ```json
701
+ {"action":"upload_file","target":"input","label":"Nota fiscal","file_path":"/projeto/.jev-browser/tmp/uploads/mcp-1234-pid-1234/nota.pdf"}
702
+ ```
441
703
 
442
704
  Para uma área de arrastar, use o papel e o nome acessível da dropzone. Para um
443
705
  botão que abre a janela nativa, use `target: "button"`; sem `target`, a presença
@@ -447,9 +709,9 @@ de `label` seleciona o input e `role`/`name` seleciona esse botão.
447
709
  {"action":"upload_file","target":"dropzone","role":"region","name":"Anexos","file_paths":["/fixtures/a.pdf","/fixtures/b.png"]}
448
710
  ```
449
711
 
450
- Se `JEV_BROWSER_UPLOAD_ROOT` não estiver definido, a ação recusa a execução. O
451
- limite evita que um plano transforme o MCP em leitor arbitrário de arquivos do
452
- computador.
712
+ O limite evita que um plano transforme o MCP em leitor arbitrário de arquivos
713
+ do computador; sem uma raiz explícita, só a pasta temporária isolada da sessão
714
+ é autorizada.
453
715
 
454
716
  Exemplo de chamada:
455
717
 
@@ -528,7 +790,9 @@ compartilhe esse arquivo. `storage_state` não pode ser combinado com
528
790
  `fresh_context` ou `clear_storage`.
529
791
 
530
792
  `browser.max_flow_steps` limita os passos de cada plano candidato; os planos
531
- não são somados. `browser.max_text_entry_chars` limita cada valor digitado. O
793
+ não são somados. O padrão é 24 e pode ser aumentado em `config/ui-testing.json`
794
+ ou por `JEV_BROWSER_MAX_FLOW_STEPS` (por exemplo, `30`).
795
+ `options.max_flow_steps` pode reduzir esse teto por chamada. `browser.max_text_entry_chars` limita cada valor digitado. O
532
796
  resultado contém `status`, o plano escolhido, as ações executadas, a última
533
797
  captura acessível e se o texto descritivo esperado apareceu. `expected_outcome`
534
798
  como string é descrição, não uma asserção: `expected_outcome_visible` é
@@ -547,12 +811,17 @@ quando nenhuma dessas condições se aplica; confiança do Jev, sozinha, não
547
811
  comprova o resultado. `environment_error` identifica falha de
548
812
  navegação, autenticação ou sessão do browser; `navigation_error` contém o código
549
813
  detectado, como `ERR_CONNECTION_REFUSED`, `AUTHENTICATION_REQUIRED` ou
550
- `BROWSER_DISCONNECTED`, e `reason` orienta a recuperação. Se uma navegação falhar
814
+ `BROWSER_DISCONNECTED`, `BROWSER_PAGE_NOT_RESTORED` ou
815
+ `BROWSER_PROFILE_IN_USE`, e `reason` orienta a recuperação. Se uma navegação falhar
551
816
  e a chamada seguinte tentar reutilizar a página, o MCP informa que a navegação
552
817
  anterior não carregou; reinicie a navegação com
553
818
  `continue_from_current_page: false` (ou o alias `reuse_page: false`). Uma sessão
554
- desconectada pode ser reaberta por `browser_health`; o fluxo só é repetido
555
- automaticamente uma vez se nenhuma etapa mutável tiver sido executada.
819
+ desconectada pode ser reaberta por `browser_health`. `run_browser_flow` também
820
+ tenta reconectar e repetir uma vez quando a desconexão acontece antes da primeira
821
+ etapa; a repetição exige `initial_url`. Sem essa URL, o resultado informa
822
+ `BROWSER_PAGE_NOT_RESTORED`, e chamadas posteriores em `reuse_page` continuam
823
+ recusadas até uma nova navegação explícita. Se qualquer etapa já executou ou
824
+ há token de confirmação de mutação, o MCP não repete o fluxo automaticamente.
556
825
 
557
826
  `current_url` é devolvido para diagnóstico sem query string nem fragmento; IDs
558
827
  longos no caminho também podem ser redigidos. `timings_ms.ready_ms` mede a espera
@@ -568,9 +837,14 @@ origem são capturadas mesmo quando usam transferência chunked e não enviam
568
837
  `Content-Length`. Corpos acima do limite, com formato inválido ou que não
569
838
  podem ser lidos com segurança são omitidos e explicados em `warnings`.
570
839
 
571
- `jev_browser_mcp.browser.max_action_timeout_seconds` limita esperas por ações,
572
- seletores e condições. `JEV_BROWSER_UPLOAD_ROOT` libera uploads somente dentro
573
- de uma pasta absoluta escolhida pelo operador.
840
+ `jev_browser_mcp.browser.max_action_timeout_seconds` limita esperas por ações,
841
+ seletores e condições. `JEV_BROWSER_PROJECT_ROOT` troca o diretório de projeto
842
+ usado para staging quando o diretório de trabalho do MCP não é o projeto. A
843
+ pasta `.jev-browser/tmp/uploads` deve ser tratada como temporária e ignorada
844
+ pelo Git. `JEV_BROWSER_UPLOAD_ROOT` adiciona uma raiz absoluta escolhida pelo
845
+ operador para arquivos fornecidos pelo harness; o MCP não apaga arquivos dessa
846
+ raiz. O staging privado do projeto continua ativo para fixtures e multipart
847
+ gerados pelo MCP.
574
848
  `jev_browser_mcp.browser.max_upload_files`, `max_upload_path_chars`,
575
849
  `max_upload_file_bytes` e `max_upload_total_bytes` limitam quantidade e tamanho.
576
850
  Não configure a raiz como o disco inteiro ou a pasta home: use uma pasta de
@@ -584,8 +858,9 @@ continua informada mesmo quando a lista é truncada.
584
858
 
585
859
  O `run_browser_flow` aceita `options` com `fast_path`, `snapshot_scope`,
586
860
  `block_trackers`, `block_fonts`, `local_only`, `busy_selectors`,
587
- `auto_angular_idle`, `login_url_contains`, `login_text`, `capture_console_errors`,
588
- `capture_network_errors`, `capture_network_error_bodies`, `ready_timeout_seconds`,
861
+ `auto_angular_idle`, `login_url_contains`, `login_text`, `capture_console_errors`,
862
+ `capture_network_errors`, `capture_network_error_bodies`, `capture_network`,
863
+ `capture_network_url_contains`, `ready_timeout_seconds`,
589
864
  `ready_network_idle`, `ready_stable_ms`, `ready_text`, `ready`, `continue_from_current_page`,
590
865
  `reuse_page`, `reuse_page_match`, `step_timeout_seconds`, `max_flow_steps`, `return_snapshot`,
591
866
  `screenshot_on_failure`, `screenshot_on_success`, `trace_on_failure`,
@@ -642,6 +917,16 @@ uma lista combina condições, e todas precisam passar. `has_value` aceita
642
917
  `ready_network_idle: true` solicita uma espera best-effort por `networkidle`.
643
918
  `ready_timeout_seconds` limita a espera de inicialização e `ready_stable_ms`
644
919
  define a janela usada para considerar o snapshot estável.
920
+
921
+ Se a duração prevista ultrapassar o timeout de chamada do harness, passe
922
+ `background: true` em `run_browser_flow`. A chamada retorna um `run_id`; consulte
923
+ `get_browser_flow_result` com esse ID e, opcionalmente, `wait_ms` de até 30000
924
+ para aguardar a conclusão, mantendo esse valor abaixo do timeout do harness.
925
+ No resultado da consulta, `status` indica se a execução terminou; o status do
926
+ fluxo em si fica em `result.status`. Os resultados ficam na memória do processo
927
+ MCP por 30 minutos e saem antes se o limite de execuções retidas for atingido.
928
+ Reiniciar o servidor apaga execuções e resultados pendentes. O modo síncrono
929
+ permanece disponível para fluxos curtos.
645
930
 
646
931
  `console_levels` aceita `log`, `info`, `debug`, `warn` e `error`. Com
647
932
  `console_levels: ["log","warn","error"]` e `capture_network_errors`, o retorno
@@ -649,6 +934,8 @@ traz mensagens do console agrupadas por texto e nível (`console_messages` com
649
934
  `count`), além de `console_errors` para compatibilidade. `network_failures`
650
935
  contém eventos limitados em quantidade e tamanho. Mensagens idênticas do console
651
936
  são agrupadas em uma entrada com `count`.
937
+ Controles sem nome acessível aparecem em um único aviso com a contagem e alguns
938
+ exemplos, em vez de gerar uma mensagem por controle.
652
939
  Respostas HTTP 4xx/5xx e requisições que falham são listadas; query strings,
653
940
  fragmentos, valores de formulário e nomes de arquivo são removidos ou
654
941
  sanitizados. `capture_network_error_bodies: true` lê somente respostas JSON
@@ -689,8 +976,9 @@ Para testar microfone, conceda permissão no escopo da chamada:
689
976
  ```
690
977
 
691
978
  `fake_media` aceita tanto o caminho WAV direto quanto o objeto
692
- `{"audio":"caminho.wav"}`. Exige um WAV RIFF válido dentro de
693
- `JEV_BROWSER_UPLOAD_ROOT` e respeita os mesmos limites de upload. Ao informar
979
+ `{"audio":"caminho.wav"}`. Exige um WAV RIFF válido dentro do staging privado
980
+ do projeto ou de `JEV_BROWSER_UPLOAD_ROOT` e respeita os mesmos limites de
981
+ upload. Ao informar
694
982
  `fake_media`, o MCP concede automaticamente a permissão `microphone`, adiciona
695
983
  as opções de dispositivo falso do Chromium e usa o arquivo como entrada de
696
984
  áudio. Sem esse arquivo, a
@@ -718,12 +1006,12 @@ Chrome/Edge; em outros sistemas, usa o `SingletonLock` do Chromium. O caminho
718
1006
  padrão já separa processos por PID; configure `JEV_BROWSER_PROFILE` para mudar
719
1007
  a raiz e `JEV_BROWSER_SESSION_ID` para nomear a instância.
720
1008
  Resultados MCP incluem `server_version`; erros também começam com a versão do
721
- servidor para facilitar a comparação entre instalações. A ferramenta
722
- `browser_health` informa se a sessão está ativa e tenta reconectar um browser
723
- que encerrou desde a chamada anterior. Passe `{"restart":true}` para fechar e
724
- reiniciar somente o navegador administrado por este processo MCP. Um fluxo pode
725
- ser repetido automaticamente uma vez após `BROWSER_DISCONNECTED` quando nenhuma
726
- etapa mutável foi executada.
1009
+ servidor para facilitar a comparação entre instalações. A ferramenta
1010
+ `browser_health` informa se a sessão está ativa e tenta reconectar um browser
1011
+ que encerrou desde a chamada anterior. Passe `{"restart":true}` para fechar e
1012
+ reiniciar somente o navegador administrado por este processo MCP. Perfil ocupado
1013
+ é devolvido como `BROWSER_PROFILE_IN_USE` com o PID detectado e não provoca
1014
+ encerramento do Chrome/Edge existente.
727
1015
 
728
1016
  `harness_browser` e `computer_browser` aceitam `chrome` ou `msedge`.
729
1017
  `computer_user_data_dir` deve ficar fora do repositório e conter `{browser}` e
@@ -11,7 +11,10 @@ Usage:
11
11
 
12
12
  Configuration is read from config/ui-testing.json. Set OPENROUTER_API_KEY in
13
13
  the environment. The default mode is computer; use JEV_BROWSER_MODE=harness to select isolated headless mode.
14
- For upload_file, set JEV_BROWSER_UPLOAD_ROOT to a dedicated fixture directory.
14
+ browser_health reports the project-local upload staging directory by default.
15
+ Set JEV_BROWSER_UPLOAD_ROOT for a dedicated persistent fixture directory.
16
+ JEV_BROWSER_MAX_FLOW_STEPS, JEV_BROWSER_MAX_UPLOAD_FILE_BYTES and
17
+ JEV_BROWSER_MAX_UPLOAD_TOTAL_BYTES override the corresponding configured caps.
15
18
  `;
16
19
 
17
20
  async function main() {