@diegosouzacdv/jev-browser-mcp 0.7.1 → 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.
- package/README.md +280 -55
- package/config/ui-testing.json +31 -10
- package/docs/jev-browser-mcp.md +280 -55
- package/mcp_servers/jev-browser-npm/bin/jev-browser-mcp.cjs +4 -1
- package/mcp_servers/jev-browser-npm/src/browser-flow-runs.mjs +57 -0
- package/mcp_servers/jev-browser-npm/src/config.mjs +136 -37
- package/mcp_servers/jev-browser-npm/src/environment-profiles.mjs +241 -0
- package/mcp_servers/jev-browser-npm/src/flow.mjs +650 -201
- package/mcp_servers/jev-browser-npm/src/server.mjs +136 -35
- package/mcp_servers/jev-browser-npm/src/test-integrations.mjs +655 -0
- package/mcp_servers/jev-browser-npm/src/test-suites.mjs +438 -0
- package/mcp_servers/jev-browser-npm/src/upload-staging.mjs +80 -0
- package/package.json +7 -1
package/docs/jev-browser-mcp.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,24 +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 `
|
|
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
92
|
aliases, placeholders, campos obrigatórios de `flow`/plano e requisitos
|
|
93
93
|
condicionais por ação. `run_browser_flow` agrega campos obrigatórios ausentes
|
|
94
94
|
em todos os planos e etapas antes de abrir o navegador. Consulte essas ferramentas
|
|
95
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
|
|
98
|
-
raiz local configurada e reações idempotentes a um
|
|
99
|
-
|
|
100
|
-
|
|
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,
|
|
101
102
|
aceita `label`, `placeholder`, `title`, `text`, `test_id` e `selector`; CSS/XPath
|
|
102
103
|
são o último recurso e geram um aviso no resultado. Ele usa a
|
|
103
|
-
biblioteca Playwright diretamente, sem iniciar um segundo servidor MCP do
|
|
104
|
-
Playwright. O transporte MCP usa `stdio`; toda saída de diagnóstico vai para
|
|
105
|
-
`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.
|
|
106
131
|
|
|
107
132
|
O modo `computer` grava cookies no perfil exclusivo configurado, e conteúdo da
|
|
108
133
|
página pode conter instruções maliciosas. O Jev recebe a captura acessível com
|
|
@@ -185,11 +210,15 @@ Cada plano pode usar:
|
|
|
185
210
|
função, armazenamento local ou propriedades de credenciais. Strings e números
|
|
186
211
|
voltam diretamente; objetos e arrays retornam somente tipo, chaves e tamanho,
|
|
187
212
|
sem despejar seus valores;
|
|
188
|
-
- `http_request` executa
|
|
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`
|
|
189
217
|
retorna apenas metadados e valores redigidos; `read_angular_state` devolve o
|
|
190
|
-
nome do estado e nomes dos parâmetros, sem valores.
|
|
191
|
-
`local_only: true` e página em loopback. `http_request`
|
|
192
|
-
|
|
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`;
|
|
193
222
|
- `frame: "first"` seleciona o primeiro iframe; `frame: {"url_contains":"..."}`
|
|
194
223
|
localiza um iframe pela URL. Uma string diferente continua buscando o nome ou
|
|
195
224
|
título exato do frame;
|
|
@@ -202,6 +231,9 @@ Cada plano pode usar:
|
|
|
202
231
|
- `if_visible` em um clique torna a ação condicional e
|
|
203
232
|
`when: {"visible":"..."}` em cada plano limita as opções do Jev às que
|
|
204
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;
|
|
205
237
|
- `fill` é alias de `type`, `press_key` de `press`, `duration_ms` de `wait.ms` e
|
|
206
238
|
`value` de campos de texto. `timeout_ms` funciona por etapa. Placeholders usam
|
|
207
239
|
`{nome}`; referências desconhecidas e `{{nome}}` são recusadas antes da
|
|
@@ -215,11 +247,18 @@ Cada plano pode usar:
|
|
|
215
247
|
- `clear_storage` limpa cookies, local/session storage, IndexedDB, Cache Storage
|
|
216
248
|
e service workers do contexto atual. Como opção de fluxo,
|
|
217
249
|
`options.clear_storage: true` também recarrega a página antes do snapshot inicial;
|
|
218
|
-
- `assert_text`, `assert_value`, `
|
|
219
|
-
`assert_enabled`; `
|
|
220
|
-
|
|
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;
|
|
221
255
|
- `upload_file` para input rotulado, botão que abre o seletor de arquivo ou
|
|
222
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);
|
|
223
262
|
- `target_page: "popup"` para continuar no popup aberto com `expect_popup: true`;
|
|
224
263
|
locators semânticos também atravessam open Shadow DOM. Shadow DOM fechado não
|
|
225
264
|
é acessível ao Playwright;
|
|
@@ -238,7 +277,10 @@ As proteções e evidências por etapa usam estes campos:
|
|
|
238
277
|
- `confirm_modal` combina o clique no gatilho, a conferência do texto e o clique
|
|
239
278
|
afirmativo em uma única etapa protegida. Também aceita os aliases
|
|
240
279
|
`trigger`/`confirm`, por exemplo
|
|
241
|
-
`{"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;
|
|
242
284
|
- `mutating: true` marca etapas que gravam dados ou disparam efeitos externos.
|
|
243
285
|
O MCP também classifica botões conhecidos como `Salvar`, `Emitir`, `Cancelar`
|
|
244
286
|
ou `Sim` pelo nome e pelo controle resolvido, inclusive quando o plano usa um
|
|
@@ -266,10 +308,19 @@ booleanos. Use `{pedido}` em `flow`, `expected_outcome` e nos campos textuais do
|
|
|
266
308
|
plano para reutilizar um valor sem editar o roteiro em vários lugares. A ação
|
|
267
309
|
`extract` lê `text` (padrão), `value` ou `attribute` de um elemento e salva o
|
|
268
310
|
resultado na variável indicada por `as`; etapas seguintes podem usar
|
|
269
|
-
`{documento}`. O valor extraído é tratado como sensível e fica oculto no
|
|
270
|
-
resultado por padrão; use `sensitive: false` só quando for apropriado exibi-lo.
|
|
271
|
-
|
|
272
|
-
|
|
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.
|
|
273
324
|
|
|
274
325
|
A ação `assert_network` verifica respostas observadas depois da etapa anterior,
|
|
275
326
|
incluindo respostas 2xx ou erros esperados como 404. Exemplo:
|
|
@@ -290,9 +341,13 @@ da mesma origem e respeita o limite configurado para captura de corpos.
|
|
|
290
341
|
`assert_network` consulta respostas desde o marcador da etapa e usa a resposta
|
|
291
342
|
da navegação inicial como fallback quando nenhuma resposta posterior corresponde.
|
|
292
343
|
`wait_for_request` e `expected_outcome.request` consultam desde o início do fluxo.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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.
|
|
296
351
|
Um único plano `fast_path` composto apenas por `assert_network` e
|
|
297
352
|
`wait_for_request` não espera um snapshot visual, então endpoints que respondem
|
|
298
353
|
sem HTML também podem ser verificados. `navigation_http_status` registra um
|
|
@@ -478,21 +533,173 @@ asserções são aceitos. `comment` também é aceito em planos e passos, mas é
|
|
|
478
533
|
ignorado e não é enviado ao Jev. Campos fora do contrato continuam sendo
|
|
479
534
|
recusados.
|
|
480
535
|
|
|
481
|
-
### Upload de arquivos
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
+
```
|
|
496
703
|
|
|
497
704
|
Para uma área de arrastar, use o papel e o nome acessível da dropzone. Para um
|
|
498
705
|
botão que abre a janela nativa, use `target: "button"`; sem `target`, a presença
|
|
@@ -502,9 +709,9 @@ de `label` seleciona o input e `role`/`name` seleciona esse botão.
|
|
|
502
709
|
{"action":"upload_file","target":"dropzone","role":"region","name":"Anexos","file_paths":["/fixtures/a.pdf","/fixtures/b.png"]}
|
|
503
710
|
```
|
|
504
711
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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.
|
|
508
715
|
|
|
509
716
|
Exemplo de chamada:
|
|
510
717
|
|
|
@@ -583,7 +790,9 @@ compartilhe esse arquivo. `storage_state` não pode ser combinado com
|
|
|
583
790
|
`fresh_context` ou `clear_storage`.
|
|
584
791
|
|
|
585
792
|
`browser.max_flow_steps` limita os passos de cada plano candidato; os planos
|
|
586
|
-
não são somados.
|
|
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
|
|
587
796
|
resultado contém `status`, o plano escolhido, as ações executadas, a última
|
|
588
797
|
captura acessível e se o texto descritivo esperado apareceu. `expected_outcome`
|
|
589
798
|
como string é descrição, não uma asserção: `expected_outcome_visible` é
|
|
@@ -628,9 +837,14 @@ origem são capturadas mesmo quando usam transferência chunked e não enviam
|
|
|
628
837
|
`Content-Length`. Corpos acima do limite, com formato inválido ou que não
|
|
629
838
|
podem ser lidos com segurança são omitidos e explicados em `warnings`.
|
|
630
839
|
|
|
631
|
-
`jev_browser_mcp.browser.max_action_timeout_seconds` limita esperas por ações,
|
|
632
|
-
seletores e condições. `
|
|
633
|
-
de
|
|
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.
|
|
634
848
|
`jev_browser_mcp.browser.max_upload_files`, `max_upload_path_chars`,
|
|
635
849
|
`max_upload_file_bytes` e `max_upload_total_bytes` limitam quantidade e tamanho.
|
|
636
850
|
Não configure a raiz como o disco inteiro ou a pasta home: use uma pasta de
|
|
@@ -703,6 +917,16 @@ uma lista combina condições, e todas precisam passar. `has_value` aceita
|
|
|
703
917
|
`ready_network_idle: true` solicita uma espera best-effort por `networkidle`.
|
|
704
918
|
`ready_timeout_seconds` limita a espera de inicialização e `ready_stable_ms`
|
|
705
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.
|
|
706
930
|
|
|
707
931
|
`console_levels` aceita `log`, `info`, `debug`, `warn` e `error`. Com
|
|
708
932
|
`console_levels: ["log","warn","error"]` e `capture_network_errors`, o retorno
|
|
@@ -752,8 +976,9 @@ Para testar microfone, conceda permissão no escopo da chamada:
|
|
|
752
976
|
```
|
|
753
977
|
|
|
754
978
|
`fake_media` aceita tanto o caminho WAV direto quanto o objeto
|
|
755
|
-
`{"audio":"caminho.wav"}`. Exige um WAV RIFF válido dentro
|
|
756
|
-
`JEV_BROWSER_UPLOAD_ROOT` e respeita os mesmos limites de
|
|
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
|
|
757
982
|
`fake_media`, o MCP concede automaticamente a permissão `microphone`, adiciona
|
|
758
983
|
as opções de dispositivo falso do Chromium e usa o arquivo como entrada de
|
|
759
984
|
áudio. Sem esse arquivo, a
|
|
@@ -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
|
-
|
|
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() {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export function createBrowserFlowRunStore({ maxEntries, retentionMs, now = Date.now, createId = randomUUID }) {
|
|
4
|
+
const runs = new Map();
|
|
5
|
+
|
|
6
|
+
const pruneExpired = () => {
|
|
7
|
+
for (const [runId, run] of runs) {
|
|
8
|
+
if (run.status !== "running" && run.finishedAt + retentionMs <= now()) runs.delete(runId);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const makeRoom = () => {
|
|
13
|
+
pruneExpired();
|
|
14
|
+
while (runs.size >= maxEntries) {
|
|
15
|
+
const completed = [...runs].find(([, run]) => run.status !== "running");
|
|
16
|
+
if (!completed) throw new Error("all background flow slots are in use");
|
|
17
|
+
runs.delete(completed[0]);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const snapshot = (run) => ({
|
|
22
|
+
run_id: run.id,
|
|
23
|
+
status: run.status,
|
|
24
|
+
elapsed_ms: Math.max(0, now() - run.startedAt),
|
|
25
|
+
...(run.finishedAt === undefined ? {} : { finished_at: new Date(run.finishedAt).toISOString() }),
|
|
26
|
+
...(run.result === undefined ? {} : { result: run.result }),
|
|
27
|
+
...(run.error === undefined ? {} : { error: run.error }),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return Object.freeze({
|
|
31
|
+
start(execute) {
|
|
32
|
+
if (typeof execute !== "function") throw new TypeError("background flow must be a function");
|
|
33
|
+
makeRoom();
|
|
34
|
+
const run = { id: createId(), status: "running", startedAt: now() };
|
|
35
|
+
runs.set(run.id, run);
|
|
36
|
+
run.finished = Promise.resolve().then(execute).then((result) => {
|
|
37
|
+
run.result = result;
|
|
38
|
+
run.status = "completed";
|
|
39
|
+
}, (error) => {
|
|
40
|
+
run.error = String(error?.message || error).slice(0, 1000);
|
|
41
|
+
run.status = "failed";
|
|
42
|
+
}).finally(() => {
|
|
43
|
+
run.finishedAt = now();
|
|
44
|
+
});
|
|
45
|
+
return { run_id: run.id, status: run.status };
|
|
46
|
+
},
|
|
47
|
+
async get(runId, waitMs = 0) {
|
|
48
|
+
pruneExpired();
|
|
49
|
+
const run = runs.get(runId);
|
|
50
|
+
if (!run) return undefined;
|
|
51
|
+
if (run.status === "running" && waitMs > 0) {
|
|
52
|
+
await Promise.race([run.finished, new Promise((resolve) => setTimeout(resolve, waitMs))]);
|
|
53
|
+
}
|
|
54
|
+
return snapshot(run);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|