import { expect, test } from '@playwright/test'; test.describe('Página de música', () => { test('los estilos están siempre a la vista, con su recuento', async ({ page }) => { await page.goto('/musica'); const lateral = page.getByRole('navigation', { name: 'Estilos' }); await expect(lateral).toBeVisible(); await expect(lateral.getByRole('link', { name: /Baladas románticas/ })).toBeVisible(); await expect(lateral.getByRole('link', { name: /Clásica/ })).toBeVisible(); }); test('buscar y elegir vista son una sola pieza', async ({ page }) => { /* * Eran dos cajas con un hueco entre ellas y parecían dos controles sin * relación. El borde es de la barra: dentro, el campo y los botones no * tienen canto propio y llegan al mismo alto. */ await page.setViewportSize({ width: 1100, height: 800 }); await page.goto('/musica'); const medidas = await page.evaluate(() => { const campo = document.querySelector('input[type="search"]')!; const activo = document.querySelector('.vista.oro')!; const barra = document.querySelector('.barra')!.getBoundingClientRect(); return { bordeDelCampo: getComputedStyle(campo).borderTopWidth, altoCampo: Math.round(campo.getBoundingClientRect().height), altoBoton: Math.round(activo.getBoundingClientRect().height), altoBarra: Math.round(barra.height), dentro: activo.getBoundingClientRect().right <= barra.right + 1 }; }); expect(medidas.bordeDelCampo).toBe('0px'); expect(medidas.altoCampo).toBe(medidas.altoBoton); // Sin hueco: la barra mide lo que miden sus piezas, más su propio canto. expect(medidas.altoBarra).toBeLessThanOrEqual(medidas.altoCampo + 2); expect(medidas.dentro).toBe(true); }); test('la vista activa lleva el oro de los botones, no un color plano', async ({ page }) => { await page.goto('/musica'); const oro = page.locator('.vista.oro'); await expect(oro).toHaveText('Tarjetas'); // El degradado es lo que se pedía: al lado hay un botón dorado de verdad // y se notaba que el conmutador no era el mismo oro. const pintura = await oro.evaluate((e) => { const s = getComputedStyle(e); return { imagen: s.backgroundImage, filo: s.boxShadow, color: s.color }; }); expect(pintura.imagen).toContain('linear-gradient'); expect(pintura.filo).toContain('inset'); // Y la letra es la del botón dorado, no el gris del inactivo: el `.vista` // de la página le ganaba por especificidad al `.oro` general. const inactivo = await page .getByRole('button', { name: 'Tabla' }) .evaluate((e) => getComputedStyle(e).color); expect(pintura.color).not.toBe(inactivo); // Y cambia con la vista. await page.getByRole('button', { name: 'Tabla' }).click(); await expect(page.locator('.vista.oro')).toHaveText('Tabla'); }); test('en un móvil la barra se apila sin estirar el campo', async ({ page }) => { // En columna, `flex-basis` mide el alto: las doce rem que dan ancho al // campo lo convertían en un cajón de doscientos píxeles. await page.setViewportSize({ width: 360, height: 800 }); await page.goto('/musica'); const alto = await page .locator('input[type="search"]') .evaluate((e) => Math.round(e.getBoundingClientRect().height)); expect(alto).toBeLessThan(60); }); test('la tabla ordena por la columna que se pulsa', async ({ page }) => { await page.goto('/musica'); await page.getByRole('button', { name: 'Tabla' }).click(); const columna = page.getByRole('columnheader', { name: 'Duración' }); await expect(columna).toHaveAttribute('aria-sort', 'none'); // Las cifras empiezan de mayor a menor: lo más largo arriba. await columna.getByRole('button').click(); await expect(columna).toHaveAttribute('aria-sort', 'descending'); // Por su clase y no por su posición: la tabla lleva portada, y el número // de columna cambia con lo que se enseñe en cada página. const duraciones = await page.locator('tbody td.duracion').allInnerTexts(); const enSegundos = duraciones.map((d) => { const [m, s] = d.trim().split(':').map(Number); return m * 60 + s; }); expect(enSegundos).toEqual([...enSegundos].sort((a, b) => b - a)); // Y volver a pulsarla le da la vuelta. await columna.getByRole('button').click(); await expect(columna).toHaveAttribute('aria-sort', 'ascending'); }); test('la búsqueda encuentra por etiqueta, no solo por título', async ({ page }) => { await page.goto('/musica'); await page.getByLabel('Buscar un tema').fill('desamor'); // Ningún tema se llama «desamor»: si aparecen, es por sus etiquetas. const estado = page.getByRole('status'); await expect(estado).toContainText('para «desamor»'); await expect(page.getByRole('heading', { name: 'Media verdad' })).toBeVisible(); }); test('una búsqueda sin resultados lo dice', async ({ page }) => { await page.goto('/musica'); await page.getByLabel('Buscar un tema').fill('zzzzz'); await expect(page.getByRole('status')).toContainText('0 temas'); await expect(page.getByText('Ningún tema coincide.')).toBeVisible(); }); test('en la página de un estilo se ven todos sus temas', async ({ page }) => { // No solo los destacados: si no, un tema recién añadido no se ve. await page.goto('/musica/baladas-romanticas'); await expect(page.getByRole('heading', { name: 'Todos los temas' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Dorina' })).toBeVisible(); }); }); test.describe('Etiquetas', () => { test('la ficha de un tema enlaza sus etiquetas', async ({ page }) => { await page.goto('/canciones/como-si-nada'); await page.getByRole('link', { name: 'Desamor', exact: true }).click(); await expect(page).toHaveURL('/etiquetas/desamor'); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Desamor'); }); test('una etiqueta reúne temas de discos distintos', async ({ page }) => { await page.goto('/etiquetas/desamor'); await expect(page.getByRole('heading', { name: 'Como si nada' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'La última carta' })).toBeVisible(); }); test('el índice ordena de más usada a menos', async ({ page }) => { await page.goto('/etiquetas'); const nombres = await page.locator('.nube a').allInnerTexts(); const cuentas = nombres.map((t) => Number(t.trim().split(/\s+/).pop())); expect(cuentas).toEqual([...cuentas].sort((a, b) => b - a)); }); test('una etiqueta que no existe da 404', async ({ page }) => { const respuesta = await page.goto('/etiquetas/no-existe'); expect(respuesta?.status()).toBe(404); }); });