Communitygithub.com

ssunduko/product-video

Resuelve los selectores en lenguaje natural `ask "..."` de un script `.demo` contra la aplicación en vivo, usando la extensión Claude en Chrome, y escribe el sidecar <script>.selectors.json. Úsalo cuando un script tenga intenciones sin resolver, cuando la compilación o grabación falle con "selector(s) still state an intent", o cuando un rediseño haya roto selectores que funcionaban antes.

¿Qué es product-video?

Esta habilidad resuelve selectores de lenguaje natural en scripts `.demo` para la automatización de videos de productos, garantizando que los scripts que se ejecutan en navegadores reales tengan elementos DOM concretos vinculados a intenciones abstractas como "ask 'the message box at the bottom of the chat'". El proceso implica analizar el script para obtener intenciones no resueltas, cargar la aplicación de destino en Chrome a través de la extensión Claude en Chrome y recorrer el flujo paso a paso para resolver cada selector en el momento exacto en que su elemento está visible, evitando así errores de tiempo de ejecución. El sistema proporciona pautas detalladas para seleccionar los handles más duraderos (como `data-testid`, `aria-label` o estructura semántica) y técnicas de verificación utilizando herramientas de JavaScript. Después de la resolución, escribe un sidecar `selectors.json` con evidencia de los resultados, y confirma que no quedan intenciones sin resolver antes de completar el proceso.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ssunduko/product-video/tree/HEAD/.claude/skills/resolve-selectors

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

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

  1. Obtén la lista de trabajo.

    cd pipeline
    node script.mjs ../scripts/<name>.demo --intents
    

    JSON en stdout: cada selector ask, su intención y su resolución actual (null si no está resuelto). Resuelve los nulos. Vuelve a resolver el resto solo si el usuario dice que el flujo está roto.

  2. Carga las herramientas del navegador en UNA llamada ToolSearch, luego llama a tabs_context_mcp antes que cualquier otra cosa. Crea una nueva pestaña con tabs_create_mcp a menos que el usuario haya señalado una existente.

  3. 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 ask en 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.demo enví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".

  4. Encuentra el elemento y elige el handle más duradero. En orden de preferencia:

    PreferirPor qué
    [data-testid=...], data-test, data-qapuestos allí para ser seleccionados; sobrevive a rediseños
    un id establebueno a menos que parezca generado (:r3a:, id-1a2b)
    botón/enlace por nombre accesible exacto → btn:Nameel btn: del driver coincide con texto, aria-label e img[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.

  5. Verifica antes de escribirlo. Para cada candidato, confirma con javascript_tool que 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.

  6. 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. spec es lo que sea que el controlador acepte: un selector raw de puppeteer, btn:Text|Alt, o ::-p-text(subcadena). evidence es lo que realmente observaste; escríbelo para que la siguiente persona pueda saber si sigue siendo válido.

  7. Confirma y devuelve el control.

    node script.mjs ../scripts/<name>.demo --intents   # espera 0 sin resolver
    node script.mjs ../scripts/<name>.demo --print     # compila
    

    Informa 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íntomaCausa / solución
El elemento no está en la páginaAparece más tarde en el flujo. Recorre los beats en orden; resuelve cada intención cuando su elemento está realmente en pantalla
Dos elementos coincidenLa 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ónEstá basado en contenido generado o una clase generada. Reancla en un testid, un rol o un contenedor
Un ancla wait ... goneEstos 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ñoElimina solo esa clave del sidecar y vuelve a ejecutar. El .demo no cambia; ese es el punto
Un diálogo bloquea la extensiónNo hagas clic en cosas que abren confirm/alert. Un diálogo modal congela la extensión hasta que se descarte manualmente

Skills relacionados