YouTube Like Shortcut 1.5.1: tests con el HTML real de YouTube
La versión 1.5.1 cumple lo que dejé pendiente. Tests que cargan capturas reales de YouTube, el botón de los Shorts encontrado por su componente y no por su texto, y seis fallos que salieron al mirar con lupa. También lo que las capturas revelaron sobre cómo funcionaba la extensión hasta ahora.
En YouTube Like Shortcut por dentro terminé con una lista de lo que haría distinto. Lo primero eran tests automáticos. Lo segundo, que encontrar el botón no dependiera del idioma de YouTube. Lo tercero, cambiar los tiempos fijos por esperar a que el botón esté listo.
La versión 1.5.1 resuelve los dos primeros y, de paso, seis fallos que no sabía que tenía. Si buscas qué hace la extensión y cómo se usa, eso está en el artículo para usuarios.
Tests con capturas, no con HTML inventado
La tentación era escribir a mano un HTML que se pareciera al de YouTube y probar contra él. El problema es que un test así comprueba que el código funciona con lo que yo creo que sirve YouTube, que es justo lo que puede estar mal.
Así que los tests cargan capturas reales. Un fragmento de código que se pega en la consola de DevTools copia la zona del botón de like de la página (el Short o la cabecera del vídeo) y la descarga como .html. Hice nueve: vídeos con la ventana ancha y estrecha, con y sin like, un Short, un directo y YouTube en alemán.
happy-dom no sabe medir
Los tests corren en happy-dom, un DOM simulado sin navegador. No calcula el layout, así que getBoundingClientRect() devuelve 0 × 0 para todo. Y la extensión usa justo eso para descartar las variantes ocultas del botón: en los tests, todos los botones parecerían ocultos.
La solución fue guardar el tamaño real en la propia captura. Antes de copiar el HTML, el fragmento mide cada botón y lo apunta en un atributo:
botones.forEach(b => {
const r = b.getBoundingClientRect()
b.setAttribute('data-fixture-rect', `${Math.round(r.width)}x${Math.round(r.height)}`)
})
En los tests, getBoundingClientRect() se sustituye por una versión que lee ese atributo:
vi.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(function (this: Element) {
const match = /^(\d+)x(\d+)$/.exec(this.getAttribute('data-fixture-rect') ?? '')
const width = match ? Number(match[1]) : 0
const height = match ? Number(match[2]) : 0
// ...
})
Así el test ve los botones con el tamaño que tenían en mi pantalla al hacer la captura: el visible mide 70 × 40 y las variantes ocultas, 0 × 0.
Estos tests no avisan solos cuando YouTube cambia. Su trabajo es otro: cuando algo falle, hago una captura nueva, la añado y el test me dice qué búsqueda ha dejado de funcionar. Y a partir de ahí, ese arreglo queda protegido.
Lo que encontraron las capturas
Con las capturas delante, lo primero que vi es que el artículo anterior describía una extensión que ya no funcionaba como yo creía.
Shorts. El artículo decía que el Short que estás viendo lleva el atributo is-active y que el like está en #like-button. Ya no existe ninguno de los dos. YouTube tiene ahora una sola barra de acciones compartida por todo el feed, y el botón vive dentro de un componente like-button-view-model.
La extensión seguía funcionando, pero por los pelos. Caía en los planes de respaldo hasta llegar al último: buscar un botón cuyo aria-label dijera like o me gusta. En alemán funcionaba de casualidad, porque la etiqueta contiene liken. En francés (J'aime) o en japonés no habría encontrado nada.
Vídeos normales. De las cuatro estrategias, #segmented-like-button y ytd-toggle-button-renderer no aparecen en ninguna captura. La que funciona es la segunda, like-button-view-model button, tanto con 2029 px de ancho como con 268. Las otras se quedan por si alguna versión de YouTube las sigue sirviendo, con un test sintético cada una.
Shorts: el botón por su componente
La búsqueda en los Shorts ahora va por estructura y deja el texto como último recurso:
export function findShortLikeButton(root: ParentNode = document): HTMLElement | null {
// Strategy 1: Legacy layout (active renderer with #like-button)
const legacy = root.querySelector('ytd-reel-video-renderer[is-active] #like-button button')
if (legacy && isButtonInteractable(legacy)) return legacy as HTMLElement
// Strategy 2: like-button-view-model (current component system); hidden variants are 0x0
const viewModelBtns = [...root.querySelectorAll('ytd-shorts like-button-view-model button')]
const viewModelMatch = pickOnScreen(viewModelBtns.filter(isButtonInteractable))
if (viewModelMatch) return viewModelMatch
// Strategy 3: Aria label search inside the Shorts player
// ...
}
Hay un test que coge una captura de Shorts, borra todas las etiquetas aria-label y comprueba que el botón sigue apareciendo. Si pasa sin texto, pasa en cualquier idioma.
El Short que está en pantalla
Al pasar de Short en Short, los anteriores siguen montados en la página, a tamaño completo, solo que por encima de la parte visible. Quedarse con el primer botón que mida más de 0 × 0 podía dar like a un Short que ya habías pasado. pickOnScreen elige el que está dentro de la ventana:
export function isInViewport(el: Element): boolean {
const rect = el.getBoundingClientRect()
return rect.bottom > 0 && rect.top < window.innerHeight
}
Seis fallos que salieron al revisar
Para escribir los tests tuve que sacar la lógica del content script a módulos pequeños (like-button, shortcut, typing-guard, click, sound) que reciben lo que necesitan como parámetros. Al leer el código pieza a pieza salieron fallos que nunca había visto. Cada uno se arregló con su test primero.
- Quitar un like se celebraba como un like. Si ya le habías dado like y pulsabas
L, se quitaba, pero sonaba el pop, salía el corazón y el contador de likes subía. Ahora la extensión miraaria-pressedantes del clic: si ya estaba pulsado, no hay sonido, ni animación, ni contador. - Los Shorts no comprobaban el like. La verificación con
aria-pressedy el reintento que conté en el artículo anterior solo se hacían en vídeos normales. Ahora los Shorts usan la misma función. - La tecla desaparecía fuera de los vídeos. En la portada, en un canal o en los resultados de búsqueda, la extensión también cancelaba la
L. Ahora la tecla solo se intercepta en Shorts, vídeos y directos. - Los directos no tenían atajo. Un directo vive en
/live/<id>, no en/watch, y la extensión no lo reconocía. La captura del directo mostró que el botón es el mismo componente que en un vídeo, así que ahora se trata igual, también con la opción Solo en Shorts. - Un
AudioContextpor cada like. El sonido creaba un contexto de audio nuevo en cada pulsación, y los navegadores limitan cuántos puede tener una página. En una sesión larga, el sonido acababa por no sonar. Ahora hay uno solo, que se crea la primera vez y se reutiliza. - Reintentar sin poder comprobar. Si el botón no tiene
aria-pressed, no hay forma de saber si el clic ha entrado, y un reintento podría quitar el like que acababas de dar. En ese caso, ahora no se reintenta.
Decidir qué hacer con la tecla
El tercer fallo obligó a ordenar algo que antes estaba repartido por el manejador del teclado. Ahora es una función pura que recibe la situación y devuelve qué hacer:
export function decideKeyAction(input: {
matches: boolean
typing: boolean
pageType: PageType
onlyShorts: boolean
navigating: boolean
repeat: boolean
}): KeyAction {
if (!input.matches) return 'ignore'
if (input.typing) return 'ignore'
if (input.pageType === 'other') return 'ignore'
if (input.pageType === 'watch' && input.onlyShorts) return 'ignore'
if (input.navigating) return 'swallow'
if (input.repeat) return 'swallow'
return input.pageType === 'shorts' ? 'like-short' : 'like-watch'
}
swallow significa cancelar la tecla sin dar like. Es lo que pasa mientras YouTube está cambiando de vídeo. Antes, en ese momento, la L se escapaba al reproductor y el vídeo avanzaba 10 segundos. También pasa si mantienes la tecla pulsada, que antes daba y quitaba el like en bucle. Al ser una función sin DOM ni eventos, cada caso tiene su test de una línea.
Lo que sigue pendiente
Los tiempos fijos siguen ahí: 400 ms de margen después de navegar, 150 ms para comprobar el clic y 250 ms para reintentar. Cambiarlos por un MutationObserver que espere a que el botón esté listo de verdad es el siguiente paso, pero quería que esta versión no cambiara nada que no tuviera un test detrás.
En los vídeos normales, la última estrategia sigue buscando por texto. Ahora pesa menos: en todas las capturas, la segunda estrategia encuentra el botón antes, también con YouTube en alemán.
Lo que me enseñó
Lo más útil de esta versión no fueron los tests en sí, sino hacer las capturas. Llevaba meses explicando cómo funcionaba la extensión en los Shorts, y la explicación había dejado de ser cierta sin que nada fallara a la vista. Funcionaba por un plan de respaldo y por la suerte de que liken contiene like.
Con una herramienta que hace una sola cosa sobre una web que no controlas, lo que crees que pasa y lo que pasa se separan poco a poco. Una captura real tarda dos minutos y lo dice en seguida.
Puedes instalar YouTube Like Shortcut desde la Chrome Web Store.