@diegosouzacdv/jev-browser-mcp 0.6.1 → 0.7.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.
package/README.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.6.1"],
20
+ "args": ["--yes", "@diegosouzacdv/jev-browser-mcp@0.7.0"],
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.6.1
35
+ npm install @diegosouzacdv/jev-browser-mcp@0.7.0
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.6.1
43
+ npm install --global @diegosouzacdv/jev-browser-mcp@0.7.0
44
44
  ```
45
45
 
46
46
  Depois configure o servidor MCP com `command: "jev-browser-mcp"` e `args: []`.
@@ -49,14 +49,23 @@ Para atualizar, rode `npm install --global
49
49
 
50
50
  O padrão `computer` exige Chrome ou Edge instalado. Se optar por `harness`,
51
51
  configure `JEV_BROWSER_MODE=harness` antes de instalar o navegador gerenciado
52
- pelo Playwright com `npx --yes @diegosouzacdv/jev-browser-mcp@0.6.1 --install-browser`.
53
-
54
- O padrão é `computer`: o pacote abre o Chrome/Edge instalado e usa um perfil
55
- persistente exclusivo em `browser.computer_user_data_dir`. No modo `harness`, a
56
- instalação baixa uma vez o Chrome/Edge que o Playwright controlará. Personalize
57
- as escolhas com `JEV_BROWSER_MODE`, `JEV_BROWSER_CHANNEL` e
58
- `JEV_BROWSER_PROFILE`. O browser permanece aquecido enquanto o processo MCP
59
- estiver ativo e fecha quando o harness encerra o processo. `JEV_PROVIDER_URL`
52
+ pelo Playwright com `npx --yes @diegosouzacdv/jev-browser-mcp@0.7.0 --install-browser`.
53
+
54
+ O padrão é `computer`: o pacote abre o Chrome/Edge instalado e usa um perfil
55
+ persistente exclusivo por processo MCP em `browser.computer_user_data_dir`. O
56
+ template aceita `{browser}` e `{session}`; por padrão, `{session}` é `pid-<PID>`.
57
+ Use um `JEV_BROWSER_SESSION_ID` único por instância para manter um nome estável
58
+ entre reinicializações. Um
59
+ `JEV_BROWSER_PROFILE` explícito também recebe o sufixo da sessão, a menos que o
60
+ caminho contenha `{session}`. Assim, processos MCP independentes não disputam o
61
+ mesmo perfil. Clientes conectados ao mesmo processo MCP compartilham a sessão;
62
+ para isolamento entre harnesses, execute uma instância por sessão. No modo
63
+ `harness`, a instalação baixa uma vez o Chrome/Edge que o Playwright controlará.
64
+ Personalize as escolhas com `JEV_BROWSER_MODE`, `JEV_BROWSER_CHANNEL` e
65
+ `JEV_BROWSER_PROFILE`. Sem um ID estável, o perfil é específico do processo e
66
+ uma reinicialização começa outra sessão. O browser permanece aquecido enquanto
67
+ o processo MCP estiver ativo e fecha quando o harness encerra o processo.
68
+ `JEV_PROVIDER_URL`
60
69
  e `JEV_MODEL` podem substituir os valores centrais; a credencial continua no
61
70
  nome de variável declarado em `jev.credential_env`.
62
71
 
@@ -76,8 +85,12 @@ publicada.
76
85
 
77
86
  ### Contrato do pacote
78
87
 
79
- O executável oferece `browser_health`, `choose_next_action` e `run_browser_flow`, mantém uma
80
- sessão do browser por processo e reutiliza essa sessão entre chamadas. O plano
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
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
81
94
  passado ao Jev continua declarativo: clique, preenchimento, seleção nativa,
82
95
  hover, espera, teclas aprovadas, asserções por elemento, upload restrito a uma
83
96
  raiz local configurada e reações idempotentes a um comentário único. Também
@@ -107,11 +120,22 @@ a página inicial, captura o snapshot acessível e pede ao Jev que escolha um
107
120
  plano. Em seguida, executa o plano inteiro na mesma sessão e confere o
108
121
  resultado esperado na tela.
109
122
 
110
- Cada plano pode usar:
111
-
112
- - `click`, `type`, `hover`, `select_option`, `press`, `assert_*` e upload com
113
- papel/nome acessível ou um localizador: `label`, `placeholder`, `title`,
114
- `text`, `test_id` ou `selector`;
123
+ Cada plano pode usar:
124
+
125
+ - `navigate` para mudar de página no meio do fluxo. Com a continuidade ligada,
126
+ um `initial_url` de mesma origem diferente da URL ativa produz erro claro; use
127
+ `navigate` ou `continue_from_current_page: false` para escolher o destino;
128
+ - `click`, `check`, `uncheck`, `type`, `clear`, `hover`, `select_option`,
129
+ `press` e `assert_*` aceitam papel/nome acessível ou localizadores como
130
+ `label`, `placeholder`, `title`, `text`, `test_id` e `selector`;
131
+ - `screenshot` e `clear_storage` atuam na página/contexto atual; `upload_file`
132
+ recebe o arquivo e o alvo de upload. Os limites e formatos estão descritos nas
133
+ seções de evidências e upload;
134
+ - `navigate_menu` para percorrer uma sequência de rótulos de menu; veja a seção
135
+ de navegação de menus para o formato e as opções;
136
+ - `name_match: "exact" | "contains" | "regex"` para controlar a comparação do
137
+ nome acessível; regex rejeita construções com backtracking excessivo. O papel
138
+ `switch` funciona em cliques, verificações e asserções;
115
139
  - `near: {"text":"..."}` para localizar o controle logo depois de um texto,
116
140
  `within: {"row_containing":"..."}` para limitar por trecho e
117
141
  `within: {"row_containing_exact":"..."}` para exigir um elemento com o
@@ -125,28 +149,64 @@ Cada plano pode usar:
125
149
  espaços/glyphs de uso privado, como ícones Font Awesome; controles
126
150
  desabilitados não aparecem no diagnóstico, mas continuam contando para que o
127
151
  índice aponte ao controle correto;
128
- - `selector` com CSS ou XPath como último recurso. O MCP identifica essa escolha
129
- em `warnings`; não é permitido enviar JavaScript nem coordenadas;
152
+ - `selector` com CSS ou XPath como último recurso. O MCP identifica essa escolha
153
+ em `warnings`; não é permitido enviar JavaScript nem coordenadas;
154
+ - quando `click` falha porque o alvo está fora da viewport ou não está visível,
155
+ o MCP tenta rolar o alvo até a tela; se isso não bastar, tenta focar o controle
156
+ e enviar `Enter`, sem repetir cegamente o clique;
130
157
  - `timeout_seconds` opcional em cada etapa, limitado pela configuração central;
131
- - `type` com `mode: "keys"` para digitar sequencialmente em campos com máscara,
132
- `blur: true` para desfocar o campo e `sensitive: false` para permitir que o
133
- valor apareça nas evidências. Nesse modo, `\n` envia Enter e `\t` envia Tab;
134
- - `wait_for_text`, `wait_for_value`, `wait_for_enabled` e `wait_for_condition`
135
- (`network_idle`, `hidden`, `text_hidden` ou `angular_idle`). `wait` recebe
158
+ - `type` com `mode: "keys"` para digitar sequencialmente em campos com máscara,
159
+ `blur: true` para desfocar o campo e `sensitive: false` para permitir que o
160
+ valor apareça nas evidências. `text_env` lê o valor sensível de uma variável
161
+ de ambiente do processo MCP, sem colocá-lo no plano. Nesse modo, `\n` envia
162
+ Enter e `\t` envia Tab;
163
+ - `wait_for_text`, `wait_for_value`, `wait_for_enabled` e `wait_for_condition`
164
+ (`url`, `network_idle`, `hidden` ou `angular_idle`). `wait` recebe
136
165
  `ms` para uma pausa curta e limitada. `angular_idle` aguarda requisições
137
166
  `$http` e digest do AngularJS. `wait_for_text` aceita `fail_on: {"role":"alert"}` para
138
167
  interromper a espera assim que um alerta visível aparecer e incluir seu texto
139
168
  no erro. Se o alerta contiver o texto esperado, a etapa passa; caso contrário,
140
- o alerta interrompe a espera. `network_idle` aceita `url_contains` para
141
- aguardar só as requisições correspondentes;
142
- - `press` (o alias legado `press_key` também é aceito) com `PageDown`, `PageUp`,
143
- `Home`, `End`, setas, `Enter`, `Escape`, `Tab` ou `Shift+Tab`. Sem alvo,
144
- envia a tecla ao elemento focado; com alvo, usa o localizador informado;
169
+ o alerta interrompe a espera. `url` aguarda a URL conter `url_contains`.
170
+ `network_idle` aceita esse filtro e aguarda uma janela de silêncio usando
171
+ `ready_stable_ms`; WebSocket, EventSource e polling não relacionado não
172
+ bloqueiam a espera. Se não surgir requisição correspondente, o passo conclui
173
+ após a janela de silêncio;
174
+ - `assert_network` valida resposta HTTP observada (inclusive sucesso 2xx ou erro
175
+ esperado); `wait_for_request` aguarda uma resposta usando `*` como curinga na
176
+ URL e filtros opcionais de método/status; `assert_ws` confere texto, pares
177
+ JSON esperados ou código de fechamento de WebSocket;
178
+ - `evaluate` lê somente caminhos de propriedades como `navigator.mediaDevices`,
179
+ `document.readyState` ou `location.pathname`. Não aceita código, chamadas de
180
+ função, armazenamento local ou propriedades de credenciais. Strings e números
181
+ voltam diretamente; objetos e arrays retornam somente tipo, chaves e tamanho,
182
+ sem despejar seus valores;
183
+ - `new_tab`, `switch_tab` e `new_context` abrem/selecionam abas e criam contexto
184
+ separado dentro do fluxo; `target_page: "popup"` mantém popups abertos para
185
+ passos posteriores;
186
+ - `if_visible` em um clique torna a ação condicional e
187
+ `when: {"visible":"..."}` em cada plano limita as opções do Jev às que
188
+ correspondem à tela atual;
189
+ - `fill` é alias de `type`, `press_key` de `press`, `duration_ms` de `wait.ms` e
190
+ `value` de campos de texto. `timeout_ms` funciona por etapa. Placeholders usam
191
+ `{nome}`; referências desconhecidas e `{{nome}}` são recusadas antes da
192
+ navegação;
193
+ - `press` (o alias legado `press_key` também é aceito) com `PageDown`, `PageUp`,
194
+ `Home`, `End`, setas, `Enter`, `Escape`, `Tab`, `Shift+Tab`, `Space`,
195
+ `Backspace` ou `Delete`. `clear` limpa um campo sem exigir texto. Sem alvo,
196
+ `press` envia a tecla ao elemento focado; com alvo, usa o localizador informado;
197
+ - `screenshot` grava uma imagem local; `full_page: true` inclui a página inteira.
198
+ Também é possível pedir uma captura após qualquer etapa com `screenshot: true`;
199
+ - `clear_storage` limpa cookies, local/session storage, IndexedDB, Cache Storage
200
+ e service workers do contexto atual. Como opção de fluxo,
201
+ `options.clear_storage: true` também recarrega a página antes do snapshot inicial;
145
202
  - `assert_text`, `assert_value`, `assert_visible`, `assert_hidden` e
146
203
  `assert_enabled`; `assert_text`
147
204
  pode receber apenas `role` quando a região, como `alert`, não tem nome acessível;
148
- - `upload_file` para input rotulado, botão que abre o seletor de arquivo ou
149
- dropzone;
205
+ - `upload_file` para input rotulado, botão que abre o seletor de arquivo ou
206
+ dropzone;
207
+ - `target_page: "popup"` para continuar no popup aberto com `expect_popup: true`;
208
+ locators semânticos também atravessam open Shadow DOM. Shadow DOM fechado não
209
+ é acessível ao Playwright;
150
210
  - `audit_accessibility` com axe-core para WCAG 2.1 A/AA;
151
211
  - `like_comment` e `unlike_comment`, que localizam uma linha pelo autor e texto,
152
212
  não repetem uma reação já no estado pedido e distinguem `Curtir` de
@@ -154,19 +214,25 @@ Cada plano pode usar:
154
214
 
155
215
  As proteções e evidências por etapa usam estes campos:
156
216
 
157
- - `confirm_dialog` recebe `expected_text` e `button`. Em diálogos HTML, o MCP
158
- confere o texto antes de localizar e clicar no botão. Também trata diálogos
159
- nativos `alert`/`confirm`: precisa haver uma etapa `confirm_dialog` logo após
160
- o clique que os abre, o texto deve corresponder e um diálogo inesperado é
161
- fechado sem aceitar;
162
- - `mutating: true` marca etapas que gravam dados ou disparam efeitos externos.
217
+ - `confirm_dialog` recebe `expected_text` e `button`. Em diálogos HTML, o MCP
218
+ confere o texto antes de localizar e clicar no botão. Também trata diálogos
219
+ nativos `alert`/`confirm`: precisa haver uma etapa `confirm_dialog` logo após
220
+ o clique que os abre, o texto deve corresponder e um diálogo inesperado é
221
+ fechado sem aceitar;
222
+ - `confirm_modal` combina o clique no gatilho, a conferência do texto e o clique
223
+ afirmativo em uma única etapa protegida. Também aceita os aliases
224
+ `trigger`/`confirm`, por exemplo
225
+ `{"action":"confirm_modal","trigger":"Salvar","expected_text":"Confirma a alteração?","confirm":"OK"}`;
226
+ - `mutating: true` marca etapas que gravam dados ou disparam efeitos externos.
163
227
  O MCP também classifica botões conhecidos como `Salvar`, `Emitir`, `Cancelar`
164
228
  ou `Sim` pelo nome e pelo controle resolvido, inclusive quando o plano usa um
165
229
  seletor CSS. `options.dry_run: true` executa até a primeira etapa mutável e
166
230
  para antes dela; retorna `dry_run_stopped_before_step` e um
167
- `confirmation_token` temporário, de uso único e vinculado ao fluxo e à página.
168
- Reenvie a mesma chamada com `options.confirmation_token` para autorizar essa
169
- etapa. O MCP pausa novamente antes de cada outra etapa mutável. Sem `dry_run`,
231
+ `confirmation_token` temporário, de uso único e vinculado ao fluxo e à página.
232
+ Reenvie a mesma chamada com `options.confirmation_token` para retomar no passo
233
+ pausado sem repetir os passos anteriores; o MCP confere a rota e o alvo da
234
+ mutação antes de executá-la. Ele pausa novamente antes de cada outra etapa
235
+ mutável. Sem `dry_run`,
170
236
  o primeiro pedido de ação mutável também retorna `status: "confirmation_required"`
171
237
  e token; nenhuma etapa mutável roda sem essa autorização. O token expira após
172
238
  dez minutos por padrão;
@@ -202,20 +268,37 @@ incluindo respostas 2xx ou erros esperados como 404. Exemplo:
202
268
  }
203
269
  ```
204
270
 
205
- Essa asserção aparece na evidência como `response_status`, separado do campo
206
- `status` da etapa (`passed` ou `failed`). `message_contains` lê somente resposta
207
- da mesma origem e respeita o limite configurado para captura de corpos.
271
+ Essa asserção aparece na evidência como `response_status`, separado do campo
272
+ `status` da etapa (`passed` ou `failed`). `message_contains` lê somente resposta
273
+ 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.
279
+
280
+ Para aguardar a API antes de abrir a interface, passe
281
+ `options.wait_for_http: {"url":"http://localhost:8000/health","timeout_ms":30000}`.
282
+ Também aceita apenas a URL como string ou `{ "url": "...", "timeout_seconds": 30 }`.
283
+ O probe faz GET e tenta novamente até o limite; qualquer resposta abaixo de 500
284
+ indica que o servidor respondeu (inclusive 4xx), enquanto 5xx e erros de conexão
285
+ são repetidos. Ao expirar, `reason` inclui a última causa. Por segurança, a URL
286
+ precisa ser loopback ou usar o mesmo host de `initial_url`; isso permite, por
287
+ exemplo, Vite em `localhost:5173` e API em `localhost:8000`.
208
288
 
209
289
  Exemplo de parâmetro e extração: passe `params: {"pedido":"1528721"}`, filtre
210
290
  com `within: {"row_containing_exact":"{pedido}"}`, e use uma etapa
211
291
  `{"action":"extract","role":"cell","name":"{pedido}","within":{"row_containing_exact":"{pedido}"},"as":"documento"}`.
212
292
  A etapa seguinte pode localizar o mesmo documento com `name: "{documento}"`.
213
293
 
214
- Em `wait_for_condition` com `condition: "hidden"`, use qualquer um desses
215
- localizadores, `near` ou `within`; `text_hidden` recebe o texto direto. Para
216
- aguardar o fim de uma chamada específica, use `condition: "network_idle"` com
217
- `url_contains`; o MCP precisa observar a requisição depois da etapa anterior e
218
- esperar que ela termine.
294
+ Em `wait_for_condition` com `condition: "hidden"`, use qualquer um desses
295
+ localizadores, `near` ou `within`; para esperar texto desaparecer, use
296
+ `{"condition":"hidden","text":"Carregando..."}`. Para esperar que texto suma
297
+ antes do primeiro passo, use `options.ready: {"hidden_text":"Carregando..."}`.
298
+ Para aguardar o fim de uma chamada específica, use `condition: "network_idle"` com
299
+ `url_contains`. O filtro considera somente a atividade correspondente e termina
300
+ após a janela configurada sem atividade; também termina caso nenhuma requisição
301
+ correspondente apareça. Para validar status e corpo, prefira `assert_network`.
219
302
 
220
303
  Quando vários controles têm o mesmo papel e nome, `within` limita a busca a um
221
304
  container acessível único, como uma linha, card ou diálogo. `index` escolhe uma
@@ -223,7 +306,7 @@ ocorrência zero-based dentro desse escopo; sem `index`, o MCP exige exatamente
223
306
  um alvo. Para uma página com rótulo `for` quebrado, `label` também procura o
224
307
  controle próximo ao texto visível do rótulo.
225
308
 
226
- `within` pode combinar um container e uma linha. Use, por exemplo,
309
+ `within` pode combinar um container e uma linha. Use, por exemplo,
227
310
  `{"role":"cell","name":"Documento A","within":{"role":"table","row_containing_word":"1528721"}}`.
228
311
  `row_containing_word` usa limites de palavra para não confundir `1528721` com
229
312
  `15287210`; `row_containing_exact` continua disponível para texto de célula
@@ -258,13 +341,14 @@ Exemplos para controles legados sem nome acessível:
258
341
  {"action":"click","selector":"#save-document"}
259
342
  ```
260
343
 
261
- O reconhecimento retorna `unnamed_controls_initial` e
262
- `unnamed_controls_final`, cada um com papel, índice Playwright por papel, rótulo
263
- mais próximo e um trecho HTML sanitizado dos controles visíveis e habilitados
264
- sem nome. Nomes vazios ou compostos apenas por espaços e glyphs da área Unicode
265
- de uso privado (como ícones Font Awesome) entram nessa lista e podem ser
266
- selecionados com `name: ""` e o mesmo índice. `unnamed_controls` continua
267
- disponível como alias da lista final. O placeholder conta como nome acessível.
344
+ Por padrão, controles sem nome não são devolvidos em listas separadas. Defina
345
+ `options.include_unnamed_controls: true` para receber
346
+ `unnamed_controls_initial` e `unnamed_controls_final`, cada uma com papel,
347
+ índice Playwright por papel, rótulo mais próximo e um trecho HTML sanitizado.
348
+ Nomes vazios ou compostos apenas por espaços e glyphs da área Unicode de uso
349
+ privado (como ícones Font Awesome) entram nessa lista e podem ser selecionados
350
+ com `name: ""` e o mesmo índice. O alias redundante `unnamed_controls` foi
351
+ removido. O placeholder conta como nome acessível.
268
352
  O snapshot também resume campos de formulário com `id`, `name`, valor, estado
269
353
  desabilitado, rótulo e índice zero-based entre campos de mesmo nome. Valores
270
354
  sensíveis, como senha, token e cartão, são ocultados. `snapshot_include_hidden`
@@ -275,10 +359,14 @@ Em uma etapa `type`, `text` contém o valor a digitar; use `target_text` para
275
359
  localizar pelo texto visível próximo ao campo. Esse localizador exige `role`
276
360
  `textbox`, `searchbox` ou `combobox`.
277
361
 
278
- Por padrão, o MCP executa todos os passos antes de avaliar `expected_outcome`.
279
- Valores dentro de campos não contam como resultado visível. `stop_on_expected:
280
- true` habilita parada antecipada quando o texto esperado aparece fora dos
281
- campos; mantenha `false` para fluxos com várias etapas.
362
+ Por padrão, o MCP executa todos os passos antes de avaliar os critérios.
363
+ `expected_outcome` aceita texto descritivo ou uma asserção estruturada, por
364
+ exemplo `{"text":"Tecnologia atualizada com sucesso."}` ou
365
+ `{"request":"PUT */api/tecnologia","status":200}`. A forma estruturada pode
366
+ incluir `message_contains` e reprova se a resposta correspondente não for
367
+ observada. Valores dentro de campos não contam como texto visível.
368
+ `stop_on_expected: true` habilita parada antecipada quando o texto esperado
369
+ aparece fora dos campos; mantenha `false` para fluxos com várias etapas.
282
370
 
283
371
  Os erros de validação apontam o campo inválido e os campos aceitos. Os limites
284
372
  de `timeout_seconds` informam o máximo configurado, em vez de exigir tentativa
@@ -314,11 +402,13 @@ verificar:
314
402
  {"action":"audit_accessibility","standard":"wcag2aa"}
315
403
  ```
316
404
 
317
- O MCP usa `@axe-core/playwright` com as tags WCAG 2.0 e 2.1 A/AA. O retorno
318
- resume as violações por regra, impacto e quantidade de nós, sem copiar HTML ou
319
- trechos da página. Qualquer violação reprova esse fluxo. A auditoria automática
320
- encontra problemas comuns, mas não comprova conformidade WCAG completa; combine-a
321
- com revisão manual e testes com usuários assistivos.
405
+ O MCP usa `@axe-core/playwright` com as tags WCAG 2.0 e 2.1 A/AA. O retorno
406
+ resume as violações por regra, impacto e quantidade de nós, sem copiar HTML ou
407
+ trechos da página. A etapa falha quando encontra violações no impacto definido
408
+ por `fail_on_impact`; o padrão é `minor`, e níveis abaixo do limite são relatados
409
+ sem bloquear o fluxo. A auditoria automática encontra problemas comuns, mas não
410
+ comprova conformidade WCAG completa; combine-a com revisão manual e testes com
411
+ usuários assistivos.
322
412
 
323
413
  Para ações em iframe, acrescente `"frame": "payment-iframe"`; o valor precisa
324
414
  corresponder ao atributo `name` ou `title` do iframe. O snapshot inclui o
@@ -411,27 +501,67 @@ validadores.
411
501
 
412
502
  `browser.mode` aceita `harness` ou `computer`; o padrão é `computer`:
413
503
 
414
- - `harness` usa Chrome headless e contexto isolado, adequado a execuções do
415
- harness e CI; o estado de autenticação é descartado ao final da chamada.
416
- - `computer` abre o Chrome ou Edge instalado em modo visível e usa o diretório
417
- persistente `browser.computer_user_data_dir`, separado por navegador. Não
418
- reutiliza o perfil pessoal já aberto. Faça login uma vez nesse perfil; os
419
- cookies permanecem nele entre chamadas.
420
-
421
- `browser.max_flow_steps` limita a soma de passos declarados entre os planos e
422
- `browser.max_text_entry_chars` limita cada valor digitado. O resultado contém
423
- `status`, o plano escolhido, as ações executadas, a última captura acessível e
424
- se o critério esperado apareceu. Quando existem asserções explícitas, `status`
425
- também pode ser `passed` com todas elas satisfeitas; `assertions_passed` registra
426
- esse resultado e `expected_outcome_visible` continua descrevendo somente o
427
- texto global. `incomplete` significa que nenhum critério foi comprovado;
428
- confiança do Jev não substitui essa verificação. `timings_ms.ready_ms` mede a
429
- espera da SPA; `warnings` registra capturas vazias durante transições; e
430
- `failed_step` identifica índice, ação, localizador, timeout e erro resumido
431
- quando uma etapa falha. Cada etapa executada também registra a composição do
432
- localizador usado e, quando disponível, `resolved_target` com tag, `id`, papel,
433
- nome acessível, `title` casado e `href` sanitizado do elemento resolvido.
434
- Etapas `type` só incluem o valor final do campo quando `sensitive: false`.
504
+ - `harness` usa Chrome headless e contexto isolado, adequado a execuções do
505
+ harness e CI. O processo MCP conserva browser, contexto e página entre
506
+ chamadas; cookies, local storage e estado da página permanecem enquanto o
507
+ processo estiver ativo. Encerre o processo para descartar o contexto isolado.
508
+ - `computer` abre o Chrome ou Edge instalado em modo visível e usa o diretório
509
+ persistente `browser.computer_user_data_dir`, separado por navegador e por
510
+ processo MCP. Não
511
+ reutiliza o perfil pessoal já aberto. Faça login uma vez nesse perfil; os
512
+ cookies permanecem nele entre chamadas.
513
+
514
+ `fresh_context: true` abre um contexto descartável, sem cookies nem storage do
515
+ perfil persistente; requer `initial_url` e fecha esse contexto ao terminar. Use
516
+ `clear_storage: true` para limpar cookies, local/session storage, IndexedDB,
517
+ Cache Storage e service workers antes de recarregar a página atual. Para salvar
518
+ e reutilizar o estado de autenticação de uma conta de teste, use um nome simples
519
+ em `storage_state`:
520
+
521
+ ```json
522
+ {"options":{"storage_state":"aluno-a"}}
523
+ ```
524
+
525
+ O estado fica em `storage-state/<nome>.json` dentro da pasta de artefatos, com
526
+ permissões restritas no sistema operacional; não use uma conta real nem
527
+ compartilhe esse arquivo. `storage_state` não pode ser combinado com
528
+ `fresh_context` ou `clear_storage`.
529
+
530
+ `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
532
+ resultado contém `status`, o plano escolhido, as ações executadas, a última
533
+ captura acessível e se o texto descritivo esperado apareceu. `expected_outcome`
534
+ como string é descrição, não uma asserção: `expected_outcome_visible` é
535
+ informado separadamente, e um plano concluído pode passar mesmo se a frase não
536
+ estiver visível. Use `{ "text": "..." }` ou
537
+ `{ "request": "...", "status": 200 }` para exigir uma condição; as
538
+ verificações ficam em `expected_outcome_checks`, e uma condição não satisfeita
539
+ reprova o fluxo. As asserções `assert_*`, auditorias de acessibilidade e
540
+ downloads também aparecem em campos próprios do resultado.
541
+
542
+ Os status do fluxo incluem `passed`, `failed`, `incomplete`, `environment_error`,
543
+ `dry_run` e `confirmation_required`. O fluxo fica `passed` se não houve falha ou
544
+ pausa de mutação e o texto esperado apareceu, uma asserção/auditoria/download
545
+ passou ou todas as etapas do plano foram executadas. `incomplete` é devolvido
546
+ quando nenhuma dessas condições se aplica; confiança do Jev, sozinha, não
547
+ comprova o resultado. `environment_error` identifica falha de
548
+ navegação, autenticação ou sessão do browser; `navigation_error` contém o código
549
+ detectado, como `ERR_CONNECTION_REFUSED`, `AUTHENTICATION_REQUIRED` ou
550
+ `BROWSER_DISCONNECTED`, e `reason` orienta a recuperação. Se uma navegação falhar
551
+ e a chamada seguinte tentar reutilizar a página, o MCP informa que a navegação
552
+ anterior não carregou; reinicie a navegação com
553
+ `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.
556
+
557
+ `current_url` é devolvido para diagnóstico sem query string nem fragmento; IDs
558
+ longos no caminho também podem ser redigidos. `timings_ms.ready_ms` mede a espera
559
+ da SPA; `warnings` registra capturas vazias durante transições; e `failed_step`
560
+ identifica índice, ação, localizador, timeout e erro resumido quando uma etapa
561
+ falha. Cada etapa executada também registra a composição do localizador usado
562
+ e, quando disponível, `resolved_target` com tag, `id`, papel, nome acessível,
563
+ `title` casado e `href` sanitizado do elemento resolvido. Etapas `type` só
564
+ incluem o valor final do campo quando `sensitive: false`.
435
565
 
436
566
  Com `capture_network_error_bodies: true`, respostas JSON de erro da mesma
437
567
  origem são capturadas mesmo quando usam transferência chunked e não enviam
@@ -455,42 +585,70 @@ continua informada mesmo quando a lista é truncada.
455
585
  O `run_browser_flow` aceita `options` com `fast_path`, `snapshot_scope`,
456
586
  `block_trackers`, `block_fonts`, `local_only`, `busy_selectors`,
457
587
  `auto_angular_idle`, `login_url_contains`, `login_text`, `capture_console_errors`,
458
- `capture_network_errors`, `capture_network_error_bodies`, `ready_timeout_seconds`,
459
- `ready_network_idle`, `ready_stable_ms`, `ready_text`, `continue_from_current_page`,
460
- `reuse_page`, `step_timeout_seconds`, `max_flow_steps`, `return_snapshot`,
461
- `screenshot_on_failure`, `trace_on_failure`, `snapshot_include_hidden`,
462
- `stop_on_expected`, `dry_run`, `confirmation_token` e `report_path`. Sem override,
463
- os padrões são lidos de `jev_browser_mcp` em
464
- `config/ui-testing.json`: um plano candidato pula a chamada Decisions;
465
- captura de console e screenshot de falha ficam ligadas; trace, rede e bloqueio
466
- de recursos ficam desligados; o plano roda até o fim e campos ocultos não são
467
- acrescentados ao snapshot. O browser espera a SPA renderizar uma captura
468
- acessível estável na navegação inicial. Depois, a continuidade fica ligada por
588
+ `capture_network_errors`, `capture_network_error_bodies`, `ready_timeout_seconds`,
589
+ `ready_network_idle`, `ready_stable_ms`, `ready_text`, `ready`, `continue_from_current_page`,
590
+ `reuse_page`, `reuse_page_match`, `step_timeout_seconds`, `max_flow_steps`, `return_snapshot`,
591
+ `screenshot_on_failure`, `screenshot_on_success`, `trace_on_failure`,
592
+ `trace_on_success`, `record_video`, `snapshot_include_hidden`, `include_unnamed_controls`, `stop_on_expected`,
593
+ `dry_run`, `confirmation_token`, `report_path`, `permissions`, `fake_media`,
594
+ `viewport`, `mobile`, `device_scale_factor`, `locale`, `timezone_id`,
595
+ `color_scheme`, `geolocation`, `console_levels`, `fresh_context`,
596
+ `clear_storage`, `storage_state`, `wait_for_http` e `allow_mutations`. Sem override,
597
+ os padrões são lidos de `jev_browser_mcp` em
598
+ `config/ui-testing.json`: um plano candidato pula a chamada Decisions;
599
+ captura de console e screenshot de falha ficam ligadas; trace, rede e bloqueio
600
+ de recursos ficam desligados; o plano roda até o fim e campos ocultos não são
601
+ acrescentados ao snapshot. `return_snapshot: "diff"` é o padrão, e as listas de
602
+ controles sem nome só aparecem com `include_unnamed_controls: true`. O browser
603
+ espera a SPA renderizar uma captura acessível estável na navegação inicial.
604
+ Depois, a continuidade fica ligada por
469
605
  padrão: `continue_from_current_page: true` mantém a página e seu estado entre
470
606
  chamadas na mesma origem, não repete `page.goto` e consulta um snapshot atual
471
607
  sem esperar novamente por `load`/`networkidle`. As etapas continuam aguardando
472
608
  seus próprios alvos e condições. `reuse_page` segue como alias legado; defina
473
609
  `continue_from_current_page: false` para iniciar cada chamada em `initial_url`.
474
- Se a página atual e `initial_url` tiverem origens diferentes, o MCP navega para
475
- `initial_url`. Se forem da mesma origem, continua a página atual e avisa quando
476
- as URLs completas forem diferentes. `snapshot_scope` aceita `body`, `main` ou
477
- `dialog`; se `main` não existir, o snapshot usa `body`.
478
-
479
- `block_trackers: true` bloqueia analytics/trackers e os tipos de recurso
480
- configurados. Fontes ficam habilitadas por padrão para preservar ícones e
481
- glyphs; `block_fonts: true` bloqueia fontes separadamente. `busy_selectors`
482
- complementa `aria-busy="true"` ao aguardar overlays de carregamento.
610
+ Se a página atual e `initial_url` tiverem origens diferentes, o MCP navega para
611
+ `initial_url`. URLs da mesma origem são comparadas após normalizar e ordenar a
612
+ query string. Se a rota normalizada diferir, o MCP pede uma etapa `navigate` ou
613
+ `continue_from_current_page: false`. `reuse_page_match: "path"` ignora a query;
614
+ um plano cuja primeira etapa seja `navigate` pode trocar a rota explicitamente.
615
+ `snapshot_scope` aceita `body`, `main` ou
616
+ `dialog`; se `main` não existir, o snapshot usa `body`.
617
+
618
+ `block_trackers: true` bloqueia analytics/trackers e os tipos de recurso
619
+ configurados, mas preserva `media` quando há permissão de microfone ou
620
+ `fake_media`. Fontes ficam habilitadas por padrão para preservar ícones e
621
+ glyphs; `block_fonts: true` bloqueia fontes separadamente. `busy_selectors`
622
+ complementa `aria-busy="true"` ao aguardar overlays; elementos ocultos ou fora
623
+ da tela com esse atributo não seguram a prontidão da página.
483
624
  `auto_angular_idle` aguarda AngularJS depois de cliques e digitação quando a
484
625
  página expõe o injector. `login_url_contains` e `login_text` substituem a
485
626
  detecção padrão de autenticação. `local_only: true` recusa qualquer etapa que
486
- precisaria chamar o provedor Jev; use exatamente um plano com `fast_path: true`.
487
- `step_timeout_seconds` define o timeout padrão por etapa e cada etapa pode
488
- substituí-lo com `timeout_seconds`. `max_flow_steps` reduz o limite por chamada,
489
- sem superar o teto da configuração. `return_snapshot` aceita `full`, `diff` ou
490
- `none`; `diff` retorna linhas acessíveis novas desde o snapshot inicial.
491
-
492
- Com `capture_console_errors` e `capture_network_errors`, o retorno traz
493
- `console_errors` e `network_failures`, limitados em quantidade e tamanho.
627
+ precisaria chamar o provedor Jev; use exatamente um plano com `fast_path: true`.
628
+ `allow_mutations: true` pula a confirmação humana somente para URLs em
629
+ `localhost`, `127.0.0.1` ou `::1`; use esse modo apenas em ambientes locais
630
+ controlados.
631
+ `step_timeout_seconds` define o timeout padrão por etapa e cada etapa pode
632
+ substituí-lo com `timeout_seconds`. `max_flow_steps` reduz o limite por chamada,
633
+ sem superar o teto da configuração. `return_snapshot` aceita `diff`, `full` ou
634
+ `none`; `diff` retorna linhas acessíveis novas desde o snapshot inicial.
635
+
636
+ `options.ready` aguarda condições da aplicação antes do primeiro snapshot e da
637
+ seleção do plano. Aceita `"network_idle"`,
638
+ `{"hidden_text":"Aguarde."}` e `{"selector":"#cdTecnologia","has_value":true}`;
639
+ uma lista combina condições, e todas precisam passar. `has_value` aceita
640
+ `true` (valor não vazio), `false` (vazio) ou o valor textual exato.
641
+ `ready_text` também pode exigir uma frase no snapshot inicial;
642
+ `ready_network_idle: true` solicita uma espera best-effort por `networkidle`.
643
+ `ready_timeout_seconds` limita a espera de inicialização e `ready_stable_ms`
644
+ define a janela usada para considerar o snapshot estável.
645
+
646
+ `console_levels` aceita `log`, `info`, `debug`, `warn` e `error`. Com
647
+ `console_levels: ["log","warn","error"]` e `capture_network_errors`, o retorno
648
+ traz mensagens do console agrupadas por texto e nível (`console_messages` com
649
+ `count`), além de `console_errors` para compatibilidade. `network_failures`
650
+ contém eventos limitados em quantidade e tamanho. Mensagens idênticas do console
651
+ são agrupadas em uma entrada com `count`.
494
652
  Respostas HTTP 4xx/5xx e requisições que falham são listadas; query strings,
495
653
  fragmentos, valores de formulário e nomes de arquivo são removidos ou
496
654
  sanitizados. `capture_network_error_bodies: true` lê somente respostas JSON
@@ -506,15 +664,59 @@ compartilhe os arquivos somente se o teste permitir.
506
664
 
507
665
  Screenshots por etapa usam `screenshot: true` no próprio passo; o MCP os grava
508
666
  depois que a ação termina. `options.report_path` pode apontar para `.md` ou
509
- JUnit `.xml` dentro da raiz de artefatos. O relatório inclui a versão, tempos,
510
- localizadores resolvidos, respostas verificadas por `assert_network`, falhas de
511
- rede e caminhos dos screenshots, para anexar a um PR ou card.
667
+ JUnit `.xml` dentro da raiz de artefatos. O relatório inclui a versão, tempos,
668
+ localizadores resolvidos, respostas verificadas por `assert_network`, falhas de
669
+ rede e caminhos dos screenshots, para anexar a um PR ou card.
670
+
671
+ `screenshot_on_success` grava a captura final em fluxos aprovados;
672
+ `trace_on_success` salva trace também em sucesso e `record_video` grava vídeo
673
+ WebM. Fluxos que digitam texto sensível suprimem screenshots, traces e vídeos
674
+ para não registrar credenciais. `performance_metrics` inclui métricas do
675
+ navegador quando disponíveis. `jev_browser_mcp.browser.max_tool_response_bytes`
676
+ impõe um teto global de bytes na resposta JSON da ferramenta; se necessário,
677
+ evidências volumosas são reduzidas e marcadas com `output_truncated: true`.
678
+
679
+ Para testar microfone, conceda permissão no escopo da chamada:
680
+
681
+ ```json
682
+ {
683
+ "options": {
684
+ "permissions": ["microphone"],
685
+ "fake_media": {"audio":"fixtures/resposta-aluno.wav"},
686
+ "block_trackers": true
687
+ }
688
+ }
689
+ ```
690
+
691
+ `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
694
+ `fake_media`, o MCP concede automaticamente a permissão `microphone`, adiciona
695
+ as opções de dispositivo falso do Chromium e usa o arquivo como entrada de
696
+ áudio. Sem esse arquivo, a
697
+ permissão de microfone usa o dispositivo autorizado pelo browser; no modo
698
+ `computer`, o sistema operacional ainda pode pedir acesso ao dispositivo.
699
+
700
+ As opções de emulação aceitam `viewport: {"width": 390, "height": 844}`,
701
+ `mobile: true`, `device_scale_factor`, `locale`, `timezone_id`,
702
+ `color_scheme: "dark"` e `geolocation: {"latitude": -23.55, "longitude": -46.63}`.
703
+ Informar geolocalização concede também a permissão `geolocation`.
704
+
705
+ `audit_accessibility` aceita `scope` com seletor CSS e `fail_on_impact` com
706
+ `minor`, `moderate`, `serious` ou `critical`. O padrão é `minor`; violações
707
+ abaixo do impacto escolhido continuam no relatório, mas não bloqueiam o passo.
708
+ `violations_count` e `blocking_violations_count` separam total e bloqueadoras.
709
+
710
+ Os cliques aguardam `DOMContentLoaded` quando acionam navegação completa e
711
+ tratam a destruição do contexto durante a troca de documento como navegação em
712
+ andamento, sem repetir o clique.
512
713
 
513
714
  Se o Chrome/Edge não iniciar porque o perfil persistente já está aberto, o erro
514
715
  identifica o PID que o mantém ocupado quando o sistema consegue associar o
515
- perfil ao processo. Em Windows, o MCP consulta a linha de comando dos processos
516
- Chrome/Edge; em outros sistemas, usa o `SingletonLock` do Chromium. Configure
517
- `JEV_BROWSER_PROFILE` com outro diretório absoluto para usar uma sessão isolada.
716
+ perfil ao processo. Em Windows, o MCP consulta a linha de comando dos processos
717
+ Chrome/Edge; em outros sistemas, usa o `SingletonLock` do Chromium. O caminho
718
+ padrão já separa processos por PID; configure `JEV_BROWSER_PROFILE` para mudar
719
+ a raiz e `JEV_BROWSER_SESSION_ID` para nomear a instância.
518
720
  Resultados MCP incluem `server_version`; erros também começam com a versão do
519
721
  servidor para facilitar a comparação entre instalações. A ferramenta
520
722
  `browser_health` informa se a sessão está ativa e tenta reconectar um browser
@@ -523,11 +725,50 @@ reiniciar somente o navegador administrado por este processo MCP. Um fluxo pode
523
725
  ser repetido automaticamente uma vez após `BROWSER_DISCONNECTED` quando nenhuma
524
726
  etapa mutável foi executada.
525
727
 
526
- `harness_browser` e `computer_browser` aceitam `chrome` ou `msedge`.
527
- `computer_user_data_dir` deve ficar fora do repositório e conter `{browser}`;
528
- o diretório persistente armazena dados de login e é resolvido sob a pasta home
529
- do usuário quando começa com `~`. O Playwright MCP fica desabilitado até
530
- `ORQUESTRADOR_MCP_PLAYWRIGHT_ENABLED=1` ser configurado no ambiente do harness.
728
+ `harness_browser` e `computer_browser` aceitam `chrome` ou `msedge`.
729
+ `computer_user_data_dir` deve ficar fora do repositório e conter `{browser}` e
730
+ `{session}`;
731
+ o diretório persistente armazena dados de login e é resolvido sob a pasta home
732
+ do usuário quando começa com `~`. O Playwright MCP fica desabilitado até
733
+ `ORQUESTRADOR_MCP_PLAYWRIGHT_ENABLED=1` ser configurado no ambiente do harness.
734
+
735
+ ## Preparar smoke autenticado local
736
+
737
+ O MCP não cria a identidade nem os dados do sistema testado. Para um smoke de
738
+ administração, suba a API e a UI locais, use um banco descartável com dados
739
+ fictícios e uma identidade OIDC de teste que tenha explicitamente a claim
740
+ `administrator`. Mantenha as regras de autorização de produção iguais; um token
741
+ de operador legado não deve ser promovido para admin só para fazer o smoke.
742
+
743
+ Faça o preflight nesta ordem:
744
+
745
+ 1. `browser_health` confirma a versão, o perfil/sessão e a conexão do navegador.
746
+ Se o perfil estiver ocupado, use outro `JEV_BROWSER_SESSION_ID` ou
747
+ `JEV_BROWSER_PROFILE`; não encerre o Chrome do operador.
748
+ 2. `options.wait_for_http` aguarda a API local responder antes de abrir a UI.
749
+ Informe a URL da interface em `initial_url` e a rota de health da API no
750
+ probe.
751
+ 3. `run_browser_flow` confirma que a tela administrativa esperada está visível
752
+ e que a identidade abriu com papel administrativo. Se aparecer a tela de
753
+ login, configure a conta de teste ou o provedor OIDC local; não copie o token
754
+ para `flow`, `params` ou logs. Use `text_env` para campos secretos.
755
+ 4. Valide primeiro uma prévia read-only com `assert_*`, `assert_network` e
756
+ captura de evidência. Para um fluxo que poderia gravar dados, use
757
+ `dry_run: true`; ele para antes da primeira etapa marcada como mutável.
758
+ 5. Só execute o passo de gravação em dados fictícios, depois de conferir o texto
759
+ do diálogo com `confirm_modal`/`confirm_dialog` e autorizar o token de
760
+ confirmação. Reinicializações de serviços e consultas diretas ao banco ficam
761
+ fora do smoke de tela.
762
+
763
+ Esse preflight é um roteiro composto por `browser_health`, `wait_for_http` e
764
+ asserções da própria aplicação; não há uma ferramenta genérica que valide a
765
+ claim OIDC `administrator`. A checagem do papel precisa usar um sinal visível ou
766
+ uma asserção específica do sistema consumidor.
767
+
768
+ Um `expected_outcome` descreve o objetivo; o resultado `passed` vem das
769
+ asserções explícitas ou da conclusão do plano. Verifique
770
+ `expected_outcome_visible` separadamente quando a frase global fizer parte do
771
+ critério do teste.
531
772
 
532
773
  Defina o valor secreto na variável indicada por `jev.credential_env`, usando o
533
774
  secret manager ou ambiente do processo que inicia o harness. Não grave a chave
@@ -565,18 +806,24 @@ sob demanda podem excedê-la. O MCP devolve `total_ms`, `browser_session_ms`,
565
806
  para localizar o custo. O teto observado em um fluxo sintético local anterior
566
807
  foi 6,411 s; isso não mede o Instagram nem garante o mesmo tempo em outros sites.
567
808
 
568
- O snapshot inicial, o fluxo, o resultado esperado e as descrições dos planos
569
- são enviados ao endpoint Decisions quando há mais de uma opção. Com fast-path,
570
- um plano não gera chamada remota. Os passos, valores digitados, valores
571
- esperados pelas asserções, caminhos e conteúdo dos arquivos não são enviados.
572
- Não inclua segredos em `flow`, `expected_outcome` ou nas descrições. A resposta
573
- retorna o plano escolhido quando houver decisão, custo/confiança do provedor
574
- quando disponíveis e o snapshot final sanitizado. O fluxo passa quando o texto
575
- esperado aparece no snapshot ou quando todas as asserções declaradas passam.
576
-
577
- `network_idle` é uma espera limitada e opcional; páginas com polling ou conexões
578
- contínuas podem atingir o timeout. Prefira `wait_for_text` e asserções de
579
- elemento quando houver um sinal de interface específico.
809
+ O snapshot inicial, o fluxo, o resultado esperado e as descrições dos planos
810
+ são enviados ao endpoint Decisions quando há mais de uma opção. Antes do envio,
811
+ valores dos controles são substituídos e hrefs têm query string e fragmento
812
+ removidos; padrões de PII também são mascarados. Com fast-path, um plano não
813
+ gera chamada remota. Os passos, valores digitados, valores esperados pelas
814
+ asserções, caminhos e conteúdo dos arquivos não são enviados.
815
+ Não inclua segredos em `flow`, `expected_outcome` ou nas descrições. A resposta
816
+ retorna o plano escolhido quando houver decisão, custo/confiança do provedor
817
+ quando disponíveis e o snapshot final sanitizado. Um `expected_outcome` em
818
+ texto simples é descritivo: `expected_outcome_visible` indica separadamente se
819
+ a frase apareceu, e completar o plano pode resultar em `passed` mesmo que ela
820
+ não apareça. Use a forma estruturada para tornar texto ou resposta de rede uma
821
+ condição obrigatória; se ela não passar, o fluxo falha.
822
+
823
+ `network_idle` é uma espera limitada e opcional. Polling e conexões contínuas
824
+ não relacionadas a `url_contains` não bloqueiam o modo filtrado; prefira
825
+ `wait_for_text` ou `assert_network` quando houver sinal de interface ou uma
826
+ resposta HTTP específica.
580
827
 
581
828
  Consulte a [introdução do Jev](https://docs.typesafe.ai/introduction) e o
582
829
  [tutorial da API Decisions no OpenRouter](https://openrouter.ai/docs/guides/community/jev-tutorial)