# Windup — full documentation > Testes E2E em linguagem natural com replay determinístico — o LLM planeja uma vez, os replays rodam sem ele. ~1s, $0 por replay. This file concatenates the entire Windup documentation as clean markdown for LLM consumption. Individual pages are also available at https://windup.run/pt/docs/.md and mapped in https://windup.run/pt/llms.txt. --- # Primeiros passos **Testes E2E em linguagem natural com replay determinístico — o LLM planeja uma vez, os replays rodam sem ele.** Descreva um teste em linguagem simples — *"faça login com a conta de teste, adicione o produto X ao carrinho, finalize a compra e verifique a confirmação do pedido"* — e o Windup o transforma em um plano determinístico de ações do navegador em JSON. A partir da segunda execução, o teste faz replay **com zero chamadas ao LLM**: ~1 segundo, $0, resultados estáveis. ```bash npm i -D windupjs # Chromium is provisioned automatically (one-time, machine-wide cache) npx windup init # 3 questions → windup.config.ts + example scenario npx windup scan # index your app's routes & elements from source code npx windup new "log in as admin and create an invoice" # LLM-assisted scenario authoring npx windup run checkout # 1st run: the LLM plans · every run after: ~1s replay, $0 ``` ## Requisitos - **Node ≥ 20.** - Uma **chave de API** para o LLM planejador em `.env.local` ou `.env` (`.env.local` prevalece — use-o quando o seu `.env` estiver versionado): `GOOGLE_GENERATIVE_AI_API_KEY` para Google (padrão) ou `OPENAI_API_KEY` para OpenAI. As chaves são usadas apenas para planejar; replays do cache nunca chamam um LLM. Para usar um Chrome existente em vez do Chromium baixado automaticamente, defina `CHROME_PATH`; para pular o download por completo, defina `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1`. ## Um tour de cinco minutos O fluxo completo em um projeto novo, com o que você deve esperar ver: ```bash # 1. Install — Chromium is provisioned automatically npm i -D windupjs # 2. Initialize — 3 questions (base URL, model, scenarios dir) npx windup init # → windup.config.ts + e2e/scenarios/ + .windup/ (gitignored) # 3. Index your app from source — before anything ever runs npx windup scan # scan complete (full): framework=react-router routes=106 elements=1125 # The site map now knows your real routes and selectors; the planner # will use them instead of guessing. Re-run after big changes # (windup scan --update re-indexes only files changed since, via git). # 4. Register test credentials once — values never touch git npx windup secret set admin # hidden prompts → .env.local + mapping # 5. Author a scenario from a rough instruction npx windup new "log in with the admin account and create an invoice for ACME" # → e2e/scenarios/create-invoice-acme.json — precise task grounded in # your real screens, account referenced by name, final verification # 6. First run — the LLM plans once (~3s, ~$0.002) npx windup run create-invoice-acme # PASS create-invoice-acme cache=miss llm_calls=1 ... cost=$0.0024 # 7. Every run after — deterministic replay, zero LLM npx windup run create-invoice-acme # PASS create-invoice-acme cache=hit llm_calls=0 total=600ms cost=$0 # 8. Read results like a human, ship reports to CI npx windup run --all --summary --reporter html npx windup costs # AI spend: totals, per provider/model ``` Se uma execução falhar após uma mudança no app, o plano em cache é invalidado e replanejado automaticamente na próxima execução — **você edita cenários, não seletores.** --- # Como funciona ``` natural-language task ──▶ planner (LLM, 1 call) ──▶ JSON action plan │ trajectory cache ◀── cheap verification ◀── deterministic executor │ └──▶ subsequent runs: zero LLM, ~1s, $0 ``` A parte cara — descobrir as ações do navegador — acontece uma única vez e é transformada em dados verificáveis em cache. - **Planos são dados, não código** — JSON validado por schema; sem scripts gerados, sem condicionais. - **Verificação barata** — pós-condições de DOM/URL após cada ação. Uma verificação que falha invalida o plano em cache e dispara um replanejamento automático. - **Mapa do site** — cada execução alimenta um grafo de páginas e transições; `windup scan` popula esse grafo direto do seu código-fonte antes da primeira execução, para que o planejador use os seletores *reais* do seu app em vez de adivinhar. - **Fragmentos** — blocos de ações comprovados (ex.: login) que o planejador compõe via `{ "type": "use" }` em vez de regerar. - **Zero conhecimento do site embutido** — o motor conhece frameworks e a web, nunca o *seu* site. Todo conhecimento do site chega como entrada (cenários, config, manifesto) ou é descoberto em tempo de execução. ## Por que Windup Scripts escritos à mão são baratos de rodar, mas caros de manter. Agentes de IA por execução são fáceis de escrever, mas lentos e não determinísticos. O Windup pega a metade boa de cada um. | | Scripts à mão | Agente de IA por execução | **Windup** | |---|---|---|---| | Autoria | código + seletores à mão | linguagem simples | linguagem simples | | Custo por execução | $0 | LLM em **cada** execução | LLM apenas na **primeira** execução | | Velocidade | rápido | lento (modelo no loop) | ~1s replay | | Determinismo | alto | baixo — improvisa toda vez | alto — mesmo plano em cada replay | | App mudou | você conserta o script | pode fazer outra coisa em silêncio | verificação falha → replanejamento automático | Para os mecanismos mais profundos — fronteiras entre módulos, formatos de dados, postura de custo e segurança — veja [Arquitetura e especificação](/pt/docs/architecture). --- # Cenários Um cenário é um arquivo JSON no seu diretório de cenários (padrão `e2e/scenarios/`): ```json { "scenario_id": "checkout", "start_url": "/", "task": "Log in as the qa account, add 'Backpack' to the cart, check out and verify the order confirmation message appears.", "hints": ["Optional site-specific tips for the planner. Delete if not needed."] } ``` - `start_url` é **opcional** (padrão `/`) e deve permanecer livre de ambiente: um caminho, resolvido contra a base URL efetiva. - Termine a tarefa com **o que verificar** — isso vira a pós-condição final do plano. - Nunca coloque segredos nas tarefas. Referencie contas a partir do manifesto do projeto (veja [Credenciais de teste](/pt/docs/credentials)); o plano usará `value_ref: "ENV:VAR"` e o valor real é resolvido apenas em tempo de execução, nunca em cache. ## Dependências entre cenários (`depends_on`) Os fluxos raramente começam do zero — criar uma conta bancária exige estar logado. Declare os pré-requisitos e cada cenário permanece pequeno, focado e individualmente cacheável: ```json { "scenario_id": "create-bank-account", "depends_on": ["login"], "task": "Already on the dashboard, open Settings > Bank accounts, create an account named 'Inter' and verify it appears in the list." } ``` - As dependências rodam **na mesma sessão do navegador**, em ordem, cada uma com seu próprio cache — uma suíte aquecida faz replay da cadeia inteira com zero chamadas ao LLM. - Sem um `start_url`, o cenário dependente **continua de onde a última dependência terminou** — e no primeiro planejamento o LLM vê essa página real (o dashboard pós-login), em vez de planejar às cegas. - Cadeias funcionam (`login` → `select-company` → `create-account`), ciclos são rejeitados, e uma dependência que falha faz a execução falhar com o tipo `dependency` antes mesmo de o cenário em si começar. - Cada dependência mantém sua própria autorreparação: se o plano em cache dela quebrar, ela replaneja e recacheia — os dependentes se beneficiam automaticamente. - Editar a `task` de um cenário invalida seu plano em cache (um teste reescrito é um teste diferente). `windup new` lida com dependências das duas formas: `--depends-on login` as declara explicitamente, e **o LLM autor também as sugere por conta própria** — ele vê todos os cenários existentes (id + task) e, quando a instrução pressupõe um estado que um deles produz ("já logado…"), emite `depends_on` automaticamente (filtrado mecanicamente contra ids reais de cenários — nunca inventado). ## Autoria com `windup new` Você não precisa escrever tarefas detalhadas à mão. Dê a `windup new` uma instrução vaga e o LLM age como autor de testes — ele a reescreve em um cenário preciso e verificável usando o **mapa do site** (telas, menus e elementos reais de `windup scan` e execuções passadas) e o **manifesto do projeto** (contas referenciadas por nome, nunca credenciais literais): ```bash npx windup new "log in with the qa user, add the backpack to the cart and check out" # → e2e/scenarios/purchase-backpack-qa.json — real screen names, concrete fake # form data, account referenced as "the qa account", explicit final verification ``` Ele gera o `scenario_id`, escolhe o `start_url` a partir das rotas conhecidas (recorrendo a `/` — ele nunca inventa caminhos) e adiciona dicas de seletor do mapa quando ajudam. Adicione **`--validate`** para que ele rode o cenário gerado e, se falhar, o refine a partir da falha e tente de novo (até 3 tentativas) — você recebe de volta um cenário que *já passou uma vez*, com um cache aquecido: ```bash npx windup new "log in and create a cost center named Marketing" --validate # attempt 1: FAIL — element button:has-text('Save') not visible # attempt 2: PASSED # ✓ validated in 2 attempts — the plan is cached ``` **Credenciais na instrução nunca vão parar no arquivo do cenário**: elas são auto-registradas como uma conta nomeada (valores em `.env.local`, mapeamento em `windup.credentials.json`) e a tarefa referencia a conta — veja [Credenciais de teste](/pt/docs/credentials). Flags: `--id `, `--force` (sobrescrever), `--depends-on `, `--llm `. A saída é um arquivo para **você revisar, editar e versionar** — a autoria é assistida, o teste continua sendo seu. Uma chamada ao LLM (~$0.001), registrada no livro-razão do `windup costs` sob `authoring`. --- # Credenciais de teste Credenciais nunca ficam em arquivos de cenário, planos, no cache ou no git — apenas **referências**. Os valores ficam em `.env.local` (no gitignore) ou nos secrets de CI; o mapeamento conta → nome de ENV fica em `windup.credentials.json` (versionado — não contém valores) e é mesclado ao manifesto do projeto automaticamente. ```bash npx windup secret set admin # hidden interactive prompts → .env.local + mapping npx windup secret list # accounts + whether each ENV is set (never prints values) ``` As tarefas então referenciam a conta pelo nome — *"faça login com a conta admin"* — e os planos usam `value_ref: "ENV:WINDUP_ADMIN_PASSWORD"`, resolvido apenas em tempo de execução. `windup new` faz isso automaticamente: credenciais digitadas na instrução são detectadas, registradas e removidas — o cenário gerado menciona a conta, nunca os valores. No CI, defina os mesmos nomes de variável como secrets do pipeline. --- # Ambientes (dev / staging / CI) A origem da URL inicial vem de, em ordem de precedência: 1. flag `--base-url` 2. env `WINDUP_BASE_URL` 3. `baseUrl` em `windup.config.ts` 4. um `start_url` absoluto no cenário Um override explícito rebaseia até URLs de cenário absolutas (caminho e query são preservados). O cache de planos é **portável entre ambientes**: a identidade do cache usa o *caminho* da URL inicial, não host/porta. Um plano gerado contra `localhost:8080` faz replay em staging ou CI com zero chamadas ao LLM. ```bash npx windup run checkout --base-url https://staging.example.com WINDUP_BASE_URL=http://localhost:8080 npx windup run --all ``` --- # Provedores de LLM O planejador é agnóstico de provedor. Google Gemini e OpenAI são suportados; configure vários ao mesmo tempo e escolha um por execução: ```ts // windup.config.ts llm: { provider: "google", // default for runs without --llm model: "gemini-3.1-flash-lite", providers: { openai: { model: "gpt-5-mini" }, // default model when --llm openai is used // openai: { apiKeyEnv: "MY_OPENAI_KEY", baseUrl: "https://my-proxy/v1" }, }, }, ``` ```bash npx windup run checkout # config default (google) npx windup run checkout --llm openai # provider default model (gpt-5-mini) npx windup run checkout --llm openai:gpt-5-nano # explicit provider:model WINDUP_LLM=openai:gpt-5-mini npx windup run --all # same thing via env (CI) ``` - `--llm` funciona em `run`, `bench` (compara provedores no mesmo cenário) e `scan` (camada de LLM-assist). - Chaves de API: `GOOGLE_GENERATIVE_AI_API_KEY` / `OPENAI_API_KEY` por padrão; sobrescreva o nome da variável de ambiente com `apiKeyEnv`. - `baseUrl` (apenas OpenAI) aponta para qualquer endpoint compatível com OpenAI — Azure, um proxy ou um servidor de modelo local. - Trocar de provedor nunca invalida o cache de planos: planos são dados, replays são livres de LLM independentemente de quem planejou. - `windup costs` detalha o gasto **por provedor e por modelo**, então alternar entre LLMs mantém o gasto por fornecedor visível. --- # CI / CD ```bash npx windup run --all --reporter junit --report-file reports/windup.xml ``` - `--all` roda todos os cenários do diretório (um navegador aquecido para a suíte inteira). - O código de saída é diferente de zero quando qualquer cenário falha. - `--concurrency ` roda cenários em paralelo em um único navegador aquecido compartilhado (~2× mais rápido em uma suíte mista); `--browser firefox|webkit` roda a suíte em multi-navegador. - `--reporter junit` gera JUnit XML (GitHub Actions, GitLab e Jenkins consomem nativamente); `--reporter json` gera um resumo legível por máquina; `--reporter html` gera uma página autocontida e amigável para humanos (zero JS/dependências — suba como artefato de CI ou abra localmente). Saída padrão: `.windup/reports/`. - `windup costs --json` reporta o gasto com IA para rastreamento no pipeline. ## Exemplo: GitHub Actions ```yaml - run: npm ci && npx playwright install chromium - run: npx windup run --all --base-url http://localhost:8080 --reporter junit --report-file reports/windup.xml env: GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GEMINI_KEY }} - uses: dorny/test-reporter@v1 if: always() with: { name: windup, path: reports/windup.xml, reporter: java-junit } ``` --- # Configuração (`windup.config.ts`) ```ts import { defineConfig } from "windupjs"; export default defineConfig({ baseUrl: "http://localhost:3000", llm: { provider: "google", model: "gemini-3.1-flash-lite", // Several providers at once — pick per run with --llm (see "LLM providers"): providers: { openai: { model: "gpt-5-mini" } }, }, scenarios: "e2e/scenarios", framework: "react-router", // detected by init; used by scan // browser: "chromium", // or "firefox" / "webkit" (need: npx playwright install ) scan: { llmAssist: { enabled: true, maxCalls: 20 }, // hard cost cap per scan }, // Project manifest: team-provided knowledge injected into the planner prompt. context: { conventions: ["every interactive element has a data-testid"], credentials: { qa: { user: "ENV:QA_USER", password: "ENV:QA_PASSWORD" }, }, vocabulary: { "order": "the Order entity, screen /orders" }, }, }); ``` - **`context.credentials`** mapeia nomes de conta para referências ENV. Quando uma tarefa menciona a conta, o plano usa `value_ref` — credenciais do manifesto têm precedência mesmo que a página exiba valores, e o planejador é proibido de inventar nomes de ENV. - **LLM-assist** (camada 3 do scan) lê arquivos que as camadas estáticas não conseguiram resolver (rotas construídas dinamicamente, componentes indiretos), limitado por `maxCalls`. Os resultados são lembrados por hash de arquivo — arquivos inalterados nunca custam de novo. Os custos são registrados no livro-razão e mostrados por `windup costs`. ## O que fica onde | Caminho | Conteúdo | Versionar? | |---|---|---| | `windup.config.ts` | Configuração | ✅ | | `e2e/scenarios/*.json` | Seus testes, em linguagem natural | ✅ | | `e2e/fragments/*.json` | Blocos reutilizáveis selecionados | ✅ | | `windup.credentials.json` | Mapeamento conta → nome de ENV (sem valores) | ✅ | | `.env.local` | Valores das credenciais | ❌ (auto-gitignore; o CI usa secrets com os mesmos nomes) | | `.windup/` | Estado derivado: cache de planos, livro-razão de execuções, mapa do site, relatórios | ❌ (o init o adiciona ao `.gitignore`) | --- # API programática e test runners Rode um cenário a partir de código e receba de volta as métricas da execução: ```ts import { run } from "windupjs"; const result = await run("checkout"); // RunMetrics: result, llm_calls, cost, per-action timing ``` Integre ao **vitest** (contrato compatível com jest) — um teste nativo por cenário, compartilhando o motor aquecido: ```ts // e2e/windup.test.ts — vitest import { windupSuite } from "windupjs/vitest"; await windupSuite(); // one native test per scenario ``` --- # Comandos | Comando | Descrição | |---|---| | `windup init` | Cria `windup.config.ts`, `.windup/` (no gitignore) e um cenário de exemplo | | `windup new "" [--id x] [--force] [--depends-on ids] [--validate]` | Gera um cenário a partir de uma instrução vaga; `--validate` roda e o refina até passar (≤3 tentativas) | | `windup run [scenario]` | Roda um cenário (replay quando em cache, planeja em caso de miss) | | `windup run --all` | Roda todos os cenários — modo CI | | `windup scan [--update] [--no-assist]` | Indexa estaticamente rotas e elementos interativos no mapa do site; `--update` reindexa apenas arquivos alterados desde o último scan (git diff); `--no-assist` pula a camada de LLM (custo zero) | | `windup costs [--last n] [--days n] [--json]` | Relatório de uso de IA a partir do livro-razão de execuções: totais, replays gratuitos, detalhamento por provedor, por modelo e por cenário, gasto de scan e de autoria | | `windup status` | Páginas do mapa do site por origem, obsolescência, cenários em cache, fragmentos | | `windup fragment extract --id --description ` | Promove uma fatia de um plano em cache a um fragmento reutilizável | | `windup secret set [--user u] [--password p]` | Registra credenciais de teste: valores → `.env.local`, mapeamento → `windup.credentials.json` | | `windup secret list` | Contas + se cada ENV está definida (nunca imprime valores) | | `windup sig [--repeat n]` | Assinatura estrutural da página (diagnóstico) | | `windup bench ` | Protocolo completo de validação (geração, determinismo do replay, recuperação de falhas) | | `windup cache clear` | Descarta o cache de trajetória (as próximas execuções replanejam) | ### Flags do `run` | Flag | O que faz | |---|---| | `--all` | Roda todos os cenários do diretório — modo CI, um navegador aquecido para a suíte inteira. Código de saída diferente de zero se qualquer cenário falhar. | | `--concurrency ` | Roda até `n` cenários em paralelo em um único navegador aquecido compartilhado com contextos isolados — ~2× mais rápido em uma suíte mista. Sequencial por padrão. | | `--no-cache` | Ignora o plano em cache e replaneja do zero (força uma chamada ao LLM), mesmo quando existe uma trajetória válida. Use para regenerar um plano de propósito. | | `--no-map` | Planeja sem o grafo do mapa do site — pula as rotas e seletores indexados. Útil para depurar o planejador ou um ambiente novinho. | | `--repeat ` | Roda o cenário `n` vezes seguidas no mesmo navegador aquecido — checagens de estabilidade e instabilidade. | | `--headed` | Mostra a janela do navegador em vez de rodar headless. | | `--slowmo ` | Adiciona um atraso entre ações para você acompanhar cada passo — ritmo de demo e depuração. | | `--base-url ` | Sobrescreve a origem da URL inicial nesta execução (dev / staging / CI). Rebaseia até URLs de cenário absolutas, preservando caminho e query. | | `--browser chromium\|firefox\|webkit` | Roda no motor escolhido (Chromium por padrão). O mesmo plano faz replay nos três — escreva uma vez, rode em todos. | | `--llm ` | Escolhe o LLM planejador nesta execução (ex.: `openai:gpt-5-mini`). Afeta apenas o planejamento; replays do cache nunca chamam um LLM. | | `--summary` | Após a execução, uma chamada extra ao LLM escreve um resumo legível por humanos citando valores reais observados na página final. Desligado por padrão para que os replays fiquem em $0. | | `--suggest` | Em uma execução que **falhou**, uma chamada extra ao LLM propõe uma correção concreta para o cenário. Dispara apenas em caso de falha. | | `--reporter junit\|json\|html` | Gera um relatório de CI — JUnit XML, um resumo JSON legível por máquina ou uma página HTML autocontida. | | `--report-file ` | Escreve o relatório em um caminho específico (padrão `.windup/reports/`). | ## Resumo por IA (`--summary`) Para humanos lendo resultados (não CI), `--summary` adiciona uma chamada ao LLM após cada execução que escreve um breve resumo: o que o teste fez, o resultado, **valores concretos observados na página final** (preços, mensagens, nomes de produtos — citados literalmente da página) e quaisquer dificuldades (passos lentos, replanejamento, falhas). Ele imprime no terminal, entra no livro-razão de execuções e aparece como um bloco destacado nos relatórios HTML/JSON. ```bash npx windup run checkout --summary --reporter html # summary: "The test logged in and completed checkout for 3 items; the # confirmation page showed 'Thank you for your order'. Prices observed: ..." ``` Desligado por padrão de propósito — replays do cache ficam em zero chamadas ao LLM e $0. O custo do resumo (~$0.0005 no modelo padrão) é rastreado separadamente nas métricas da execução e incluído em `estimated_cost_usd`. ## Sugestões de correção em falhas (`--suggest`) Quando uma execução **falha**, `--suggest` adiciona uma chamada ao LLM que age como um engenheiro de QA sênior depurando-a: ele compara o plano executado e o passo que falhou contra a **página final real** e os seletores conhecidos do mapa do site, e então propõe uma correção concreta para o cenário — o seletor errado e o real, uma tela alvo que não contém o que a tarefa espera, um passo faltando ou um timeout curto demais para uma página lenta. ```bash npx windup run create-invoice --suggest # FAIL create-invoice ... element button:has-text('Save') not visible # suggested fix: The 'Save' button does not exist; the dialog's real button # is labeled 'Create'. Change the hint to button:has-text('Create'). ``` Ele transforma uma execução vermelha em uma edição específica — em vez de fazer engenharia reversa do app à mão. Dispara apenas em falha (execuções verdes não custam nada), nunca edita o cenário em si, e aparece como um bloco destacado nos relatórios HTML/JSON. Combina naturalmente com `--summary`. --- # Arquitetura e especificação Uma visão condensada da [especificação viva](https://github.com/windupjs/windup/blob/main/docs/specs/SPEC.md). Ela descreve o sistema tal como entregue. ## Tese Testes E2E de navegador podem usar um LLM **apenas para planejar** (e replanejar em caso de falha), com execução determinística e verificação barata — de modo que execuções repetidas façam **zero chamadas ao LLM**. Validado de ponta a ponta: a primeira execução planeja em segundos por frações de centavo; os replays levam ~0,5–1s e custam $0. ## Princípios 1. **O LLM é a exceção, não a regra.** Uma chamada por cache miss; replays nunca o chamam. 2. **Zero conhecimento do site embutido.** O motor pode conhecer *frameworks* (Next.js, react-router, nomenclatura de design-system) e a plataforma web — nunca um site específico. Todo conhecimento do site chega como entrada ou é descoberto em tempo de execução. 3. **Toda execução também é coleta.** O executor já visita cada página de um fluxo; persistir o que ele vê custa ~um `evaluate` por ação. 4. **Conhecimento é cache, não verdade.** Qualquer coisa que o mapa do site ou o scan estático afirme pode estar obsoleta; ela degrada para descoberta em tempo de execução. Precedência: `execution > static > llm`. 5. **Custo nunca surpreende.** Cada ponto de contato com o LLM tem um limite explícito; cada chamada é registrada em um livro-razão; trabalho estático/assist repetido é memoizado. 6. **Planos são dados, não programas.** Sem condicionais, sem loops, sem código livre. Se um fluxo precisa de ramificação, divida o cenário. ## Arquitetura ``` scenario (natural language, versioned) │ ▼ ┌─ runner ─────────────────────────┐ │ cache lookup (path-keyed) │ │ miss → planner (LLM, 1 call) │ │ hit → cached plan (rebased) │ │ → executor (deterministic) │ │ → verifier (postconditions) │─▶ fail → invalidate → re-plan │ → cache save + run ledger │ └───────────────────────────────────┘ site map (graph) ◀── windup scan (static + LLM-assist) ◀── passive collection (every run) ``` Fronteiras entre módulos (todas em `packages/windup/src/`): | Módulo | Responsabilidade | |---|---| | `browser.ts` | Fronteira única do motor (Playwright). `BrowserContext` novo por sessão sobre um singleton preguiçoso do Chromium; cliques confiáveis com actionability nativa; `ariaSnapshot()` alimenta o planejador. Mira no primeiro match *visível*, não no primeiro match do DOM. | | `llm.ts` | Fronteira multi-provedor do LLM. Uma interface `LlmClient`; implementações de Google Gemini (SDK) e OpenAI (REST puro). Vários provedores configuráveis ao mesmo tempo; selecionados por execução. Trocar de provedor nunca toca no cache de planos. | | `planner.ts` | Lógica de planejamento (agnóstica de provedor). Prompt = tarefa + árvore de a11y da página + elementos reais + fatia do mapa do site + catálogo de fragmentos + manifesto + dicas. Saída estruturada; dois níveis de retry (semântico + transitório); saída sanitizada e depois validada (o Ajv é a autoridade). | | `executor.ts` | Loop determinístico: goto → por ação (gate na visibilidade → agir → verificar). Emite observações passivas para o mapa do site. | | `verifier.ts` | Pós-condições, todas sem LLM: elemento visível, glob de URL, valor de input. Polling com esperas seguras entre frames. | | `cache.ts` | Cache de trajetória indexado por `scenario_id` + *caminho* da URL inicial (portável entre ambientes). Salvo apenas após execução completa e verificada; um replay que falha invalida e replaneja, mantendo a entrada obsoleta como evidência. | | `signature.ts` | Identidade estrutural da página: SHA-256 dos elementos interativos normalizados — sem texto, sem dados — para que ruído de ambiente não divida identidades. | | `sitemap.ts` | Grafo de páginas/transições. Nós carregam `source: execution\|static\|llm` com obsolescência e proveniência. Fatia do prompt: BFS (profundidade ≤ 3), pontuada por termos, dentro de um orçamento de caracteres. | | `scan/` | Indexação do projeto. Camada 1: rotas por convenção (Next.js, react-router). Camada 2: elementos interativos via parsing de JSX ciente de chaves (tags cruas + componentes de design-system). Camada 3: LLM-assist limitado, memoizado por hash de arquivo. | | `fragments.ts` | Subtrajetórias reutilizáveis e testadas, versionadas em `e2e/fragments/`. Planos as referenciam por id; o cache armazena a referência (atualizações se propagam). | | `authoring.ts` | `windup new` — o LLM reescreve uma instrução vaga em um cenário preciso ancorado no mapa do site e no manifesto; credenciais auto-registradas e removidas; a saída é um arquivo versionado para revisão. | | `metrics.ts` / `costs.ts` | Cada execução grava um registro no livro-razão (tokens, chamadas, modelo, tempo, classe de falha). Os preços são uma tabela por modelo datada; `windup costs` recalcula para que o histórico permaneça correto. | | `summary.ts` / `suggest.ts` | Resumo pós-execução por IA e sugestão de correção pós-falha, opcionais — uma chamada extra ao LLM cada, rastreadas separadamente, sem nunca afetar o resultado da execução. | | `reporters.ts` | Relatórios JUnit XML, JSON e HTML autocontido; saída diferente de zero em falha. | | `secrets.ts` | Credenciais sem segredos versionados: valores em `.env.local`, mapeamento conta → nome de ENV versionado; planos carregam apenas `value_ref`, resolvido em tempo de execução. | ## Formatos de dados **Plano de ações** (`plan_version: "0.1"`): `{ plan_version, scenario_id, task, start_url, generated_by, actions[] }`. Uma ação é `{ id, type: goto|click|fill|wait_for|use, target?, value? | value_ref?, url?, use?, expect?, timeout_ms }`. Regras semânticas: click/fill/wait_for exigem `target.selector`; fill exige exatamente um entre value/value_ref; `value_ref` precisa ser mencionado por task/hints/manifest (sem nomes de ENV inventados); a ação final precisa carregar `expect` (ou ser um `use`). **Entrada de cache** (`cache_version: "0.2"`): `{ key: {scenario_id, start_url(path), start_sig?}, plan, status: active|stale, stats }`. **Mapa do site** (`map_version: "0.1"`): `{ last_scan_sha, pages, transitions, assist_seen }`. ## Postura de modelo e custo Modelo planejador padrão: **`gemini-3.1-flash-lite`** (medido: 1 chamada limpa/geração, ~3–4s, ≈ $0.0025/geração). Agnóstico de provedor: Google Gemini e OpenAI vêm de fábrica por trás da fronteira `llm.ts`. Cada registro do livro-razão carrega provedor+modelo; os preços são uma tabela plana por modelo. Adicionar um fornecedor = uma implementação de cliente. ## Postura de segurança O conteúdo da página capturado do app sob teste é fornecido ao LLM **delimitado e marcado como dado não confiável**, com uma instrução explícita para tratá-lo como dado, nunca como instruções. Como os planos são dados validados por schema executados de forma determinística, uma página não pode fazer o Windup rodar código arbitrário. Valores de credenciais nunca entram em cenários, planos, no cache ou em prompts do LLM. Modelo de ameaças completo: [SECURITY.md](https://github.com/windupjs/windup/blob/main/SECURITY.md). ## Limitações conhecidas - Caminhos relativos aninhados do react-router são coletados em modo best-effort (corrigidos por observações de execução). - O scan é ciente de pacote único; monorepos são detectados e alertados, índices por app são trabalho futuro. - Sem daemon entre invocações da CLI (deliberado); o pool aquecido é por processo. - A auto-detecção de fragmentos não foi construída; a extração é manual. ## Verificação - `npm test` (packages/windup): ~97 testes de unidade/integração, herméticos ao LLM. O CI os roda com Chromium real no Ubuntu. - `windup bench `: o protocolo de validação — 5 gerações (≥ 4/5 válidas), 10 replays (10/10 com `llm_calls=0`), replay ≥ 5× mais rápido, replay a $0, recuperação de seletor quebrado. - Dogfood em projeto real: um app react-router com 106 rotas — planos fazem replay a ~0,5s/$0, cache portável entre ambientes. --- # Notas de engenharia — as técnicas por trás do Windup Um resumo das abordagens que tornam testes em linguagem natural determinísticos e baratos: - **Planeje uma vez, replay grátis.** O LLM é usado exatamente uma vez por cenário (mais o replanejamento automático quando o app muda). Sua saída é um **plano de ações em JSON validado por schema — dados, não código**: sem scripts gerados, sem condicionais, sem improviso em tempo de execução. Os replays executam o plano em cache com zero chamadas ao modelo. - **Execução determinística.** Os planos rodam no Playwright com checagens nativas de actionability e eventos de entrada confiáveis. Cada ação carrega uma pós-condição explícita (`expect`: elemento visível / glob de URL / valor de input) verificada **sem LLM** — a verificação custa uma query no DOM, não tokens. - **Cache autorreparável.** As trajetórias ficam em cache indexadas por cenário + *caminho* da URL inicial (portável entre hosts de dev/staging/CI). Uma verificação que falha invalida o plano, preserva a entrada obsoleta como evidência e dispara um replanejamento com a falha como contexto. - **Assinaturas estruturais de página.** As páginas são identificadas por um SHA-256 dos seus elementos interativos normalizados — sem texto, sem dados — para que ruído de ambiente não divida identidades, e desvios na página inicial são detectados (de forma tolerante) no replay. - **Conhecimento do site em camadas.** Um grafo de mapa do site alimenta o planejador com rotas e seletores reais, construído a partir de três fontes com precedência estrita — observação em tempo de execução > scan estático do código > LLM-assist limitado. Conhecimento é cache, não verdade: qualquer coisa obsoleta degrada para descoberta em tempo de execução. - **Disciplina de orçamento do prompt.** O prompt de planejamento mantém tamanho ≈ constante (~32k caracteres): árvore da página, fatia do mapa, catálogo de fragmentos e manifesto têm cada um orçamentos rígidos de caracteres. Prompts longos degradam mensuravelmente modelos pequenos — os orçamentos são um recurso de correção, não uma otimização. - **Normalização mecânica em vez de esperança no prompt.** A saída do modelo é sanitizada de forma determinística: campos vazios descartados, ids renumerados, `wait_for`⇄`expect` normalizados, ações de eco de fragmento deduplicadas, credenciais removidas de cenários autorados. Testes A/B entre provedores mostraram que instruções de prompt sozinhas não se sustentam entre modelos — o código tem a palavra final. - **Retry em dois níveis.** Falhas semânticas (plano inválido) recebem um retry curto carregando os erros de validação; patologias transitórias de API (degeneração por loop de tokens, rede) recebem novas chamadas com seeds variados. Retries de prompt completo são evitados — eles reativam a degeneração de forma confiável. - **Blocos de construção componíveis.** Fragmentos são subtrajetórias selecionadas e versionadas (ex.: login) que os planos referenciam por id — atualizados uma vez, propagados em todo lugar, expandidos em tempo de execução. O manifesto do projeto injeta conhecimento da equipe (convenções, contas, vocabulário) em cada plano. - **Segredos por referência.** Valores de credenciais ficam em `.env.local`/secrets de CI; arquivos versionados carregam apenas mapeamentos conta → nome de ENV. Os planos usam `value_ref`, resolvido em tempo de execução — os segredos nunca chegam ao LLM, ao cache ou ao git. - **Fronteira de LLM agnóstica de provedor.** Uma interface, implementações de Google e OpenAI (o cliente OpenAI é REST puro — sem o peso de um SDK), selecionável por execução. Trocar o motor do navegador e adicionar um provedor foram cada um uma mudança em um único arquivo — as fronteiras são a arquitetura. - **Custo que você pode auditar.** Cada ponto de contato com o LLM tem um limite explícito e entra em um livro-razão por execução com tokens, modelo e provedor; `windup costs` recalcula a partir de uma tabela de preços datada, para que o histórico permaneça correto conforme os preços mudam.