Resolver selectores en lenguaje natural
Un script .demo expresa lo que significa:
selectors:
composer = ask "the message box at the bottom of the chat"
Tu trabajo es encontrar qué es realmente eso en el DOM en vivo y escribirlo en
scripts/<name>.selectors.json, para que script.mjs pueda compilarlo en un selector
ordinario y driver.mjs pueda grabar una toma determinista.
Tú eres el resolvedor. Nada en el pipeline llama a un modelo — script.mjs
lee el sidecar y se niega a compilar sin él. La resolución ocurre aquí,
una vez, antes de grabar, porque el tiempo de la toma es la línea de tiempo del video y
un viaje de ida y vuelta del modelo durante una ejecución caería en el video terminado como
silencio muerto.
Antes de empezar
- La aplicación debe ser accesible y, si necesita inicio de sesión, ya iniciada sesión en el Chrome del usuario. Ese es el punto de usar la extensión: alcanza la sesión real, por lo que no hay ritual de SSO ni código de correo que llenar.
- Lee el script primero. Necesitas el encabezado
app:/target:para la URL, y los beats, porque los selectores deben resolverse en orden de flujo (ver más abajo).
Procedimiento
-
Obtén la lista de trabajo.
cd pipeline node script.mjs ../scripts/<name>.demo --intentsJSON en stdout: cada selector
ask, su intención y su resolución actual (nullsi no está resuelto). Resuelve los nulos. Vuelve a resolver el resto solo si el usuario dice que el flujo está roto. -
Carga las herramientas del navegador en UNA llamada ToolSearch, luego llama a
tabs_context_mcpantes que cualquier otra cosa. Crea una nueva pestaña contabs_create_mcpa menos que el usuario haya señalado una existente. -
Recorre el flujo en orden de beat. Esta es la parte que es fácil de hacer mal: muchos selectores no existen en la página de inicio. Una tabla de resultados aparece solo después de que se envía una pregunta; un control "Stop" existe solo mientras algo está generando. Así que avanza por los beats del script, resolviendo cada
asken el momento en que su elemento está en pantalla.Datos reales. Antes de cualquier paso que cree, modifique, envíe o elimine algo, detente y pregunta al usuario.
feature.demoenvía cuatro mensajes a una cuenta real de Salesmessage. Pasa el cursor en lugar de hacer clic donde solo necesitas localizar algo, y nunca hagas clic en un control destructivo para "ver qué es". -
Encuentra el elemento y elige el handle más duradero. En orden de preferencia:
Preferir Por qué [data-testid=...],data-test,data-qapuestos allí para ser seleccionados; sobrevive a rediseños un idestablebueno a menos que parezca generado ( :r3a:,id-1a2b)botón/enlace por nombre accesible exacto → btn:Nameel btn:del driver coincide con texto,aria-labeleimg[alt]interno[aria-label=...],[role=...]estable, y generalmente semántico estructura semántica ( main table,nav a[href="/teams"])sobrevive a cambios de estilo Nunca uses: cadenas
nth-child, nombres de clase generados (css-1a2b3c, pilas largas de Tailwind), ni nada basado en contenido generado. Esto último es la trampa que toda esta característica existe para evitar: la respuesta de un modelo se redacta de manera diferente en cada ejecución, por lo que un selector que coincida con su texto es un volado. Ancla en el contenedor o en una señal de finalización. -
Verifica antes de escribirlo. Para cada candidato, confirma con
javascript_toolque coincide con exactamente un elemento y que el elemento es visible:const q = 'div[data-testid="chat-input"]'; const n = document.querySelectorAll(q); console.log(JSON.stringify({ q, count: n.length, visible: n[0] ? !!(n[0].offsetWidth || n[0].offsetHeight) : false, tag: n[0]?.tagName, editable: n[0]?.isContentEditable, }));Léelo con
read_console_messages. Más de una coincidencia, o cero, significa sigue buscando — no escribas un selector que no hayas visto coincidir. -
Escribe el sidecar,
scripts/<name>.selectors.json:{ "resolvedAgainst": "https://claude.ai/new", "resolvedAt": "2026-08-10T09:00:00Z", "resolvedBy": "claude-in-chrome", "selectors": { "the message box at the bottom of the chat": { "spec": "div[data-testid=\"chat-input\"]", "evidence": "contenteditable tiptap div, aria-label 'Escribe un mensaje', 1 coincidencia, visible" } } }Clave por el texto de la intención, textual del script — renombrar un selector o reordenar el bloque no debe huérfano una resolución.
speces lo que sea que el controlador acepte: un selector raw de puppeteer,btn:Text|Alt, o::-p-text(subcadena).evidencees lo que realmente observaste; escríbelo para que la siguiente persona pueda saber si sigue siendo válido. -
Confirma y devuelve el control.
node script.mjs ../scripts/<name>.demo --intents # espera 0 sin resolver node script.mjs ../scripts/<name>.demo --print # compilaInforma lo que resolviste, entre qué tuviste que adivinar, y cualquier cosa que omitiste porque requería un paso destructivo.
Cuando no puedas resolver uno
Dilo y detente; no inventes un selector. Una intención no resuelta es un error de compilación limpio antes de que Chrome siquiera se inicie. Una incorrecta falla a mitad de la toma, después la grabación ha comenzado — el lugar costoso para fallar. Si dos elementos coinciden con la descripción igualmente bien, pregunta al usuario cuál querían decir en lugar de elegir.
Problemas comunes
| Síntoma | Causa / solución |
|---|---|
| El elemento no está en la página | Aparece más tarde en el flujo. Recorre los beats en orden; resuelve cada intención cuando su elemento está realmente en pantalla |
| Dos elementos coinciden | La descripción es ambigua. Redúcela con el usuario, luego también reformula el ask en el script para que la próxima resolución sea inequívoca |
| El selector funciona ahora, falla en la próxima ejecución | Está basado en contenido generado o una clase generada. Reancla en un testid, un rol o un contenedor |
Un ancla wait ... gone | Estos son transitorios por naturaleza (un botón Stop mientras se genera). Resuelve mientras está en pantalla, y prefiere aria-label — cambia menos que el texto |
| La resolución se desvió después de un rediseño | Elimina solo esa clave del sidecar y vuelve a ejecutar. El .demo no cambia; ese es el punto |
| Un diálogo bloquea la extensión | No hagas clic en cosas que abren confirm/alert. Un diálogo modal congela la extensión hasta que se descarte manualmente |