You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
11 KiB
251 lines
11 KiB
import { expect, test } from '@playwright/test';
|
|
import { stringify } from 'devalue';
|
|
|
|
test('la música y los letristas no enlazan fichas de géneros sin publicar', async ({ page }) => {
|
|
await page.goto('/musica');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
await page.getByRole('button', { name: 'Tabla', exact: true }).click();
|
|
await expect(page.getByRole('table')).toBeVisible();
|
|
await expect(page.locator('a[href="/generos/bolero-son"]')).toHaveCount(0);
|
|
await page.goto('/generos/letristas');
|
|
await expect(page.getByText('Bolero-son', { exact: true }).first()).toBeVisible();
|
|
await expect(page.locator('a[href="/generos/bolero-son"]')).toHaveCount(0);
|
|
});
|
|
|
|
test('el directorio de intérpretes usa una galería contenida con retratos dominantes', async ({
|
|
page
|
|
}) => {
|
|
await page.setViewportSize({ width: 1440, height: 1000 });
|
|
await page.goto('/interpretes');
|
|
|
|
const tarjeta = page.locator('.artistas .tarjeta').first();
|
|
const retrato = tarjeta.locator('.marco-retrato');
|
|
await expect(tarjeta).toBeVisible();
|
|
|
|
const medidas = await page.evaluate(() => {
|
|
const contenedor = document.querySelector<HTMLElement>('.pagina')!;
|
|
const ficha = document.querySelector<HTMLElement>('.artistas .tarjeta')!;
|
|
const imagen = ficha.querySelector<HTMLElement>('.marco-retrato')!;
|
|
return {
|
|
izquierda: Math.round(contenedor.getBoundingClientRect().left),
|
|
anchoTarjeta: Math.round(ficha.getBoundingClientRect().width),
|
|
anchoRetrato: Math.round(imagen.getBoundingClientRect().width),
|
|
altoRetrato: Math.round(imagen.getBoundingClientRect().height)
|
|
};
|
|
});
|
|
|
|
expect(medidas.izquierda).toBeGreaterThan(100);
|
|
expect(medidas.anchoTarjeta).toBeLessThan(450);
|
|
expect(medidas.anchoRetrato).toBe(medidas.anchoTarjeta - 2);
|
|
expect(medidas.altoRetrato).toBeGreaterThan(medidas.anchoRetrato);
|
|
await expect(retrato).toContainText('SP');
|
|
});
|
|
|
|
test('contacto conserva el mensaje ante un fallo de red y permite reintentar', async ({ page }) => {
|
|
await page.goto('/contacto');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
await page.route('**/contacto', (ruta) =>
|
|
ruta.request().method() === 'POST' ? ruta.abort() : ruta.continue()
|
|
);
|
|
await page.getByLabel('Nombre', { exact: true }).fill('Prueba local');
|
|
await page.getByLabel('Correo electrónico').fill('prueba@example.test');
|
|
await page.getByLabel('Motivo').selectOption('colaboracion');
|
|
await page
|
|
.getByLabel('Mensaje', { exact: true })
|
|
.fill('Este mensaje se conserva y no se envía fuera de la prueba.');
|
|
await page.getByRole('button', { name: 'Enviar mensaje' }).click();
|
|
await expect(page.getByRole('alert')).toContainText('Conservamos tu mensaje');
|
|
await expect(page.getByLabel('Mensaje', { exact: true })).toHaveValue(
|
|
'Este mensaje se conserva y no se envía fuera de la prueba.'
|
|
);
|
|
await expect(page.getByLabel('Correo electrónico')).toHaveValue('prueba@example.test');
|
|
await expect(page.getByRole('button', { name: 'Enviar mensaje' })).toBeEnabled();
|
|
await expect(page).toHaveURL('/contacto');
|
|
});
|
|
|
|
test('el acceso conserva el correo si falla la conexión', async ({ page }) => {
|
|
await page.goto('/entrar');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
await page.route('**/entrar?*/solicitar', (ruta) => ruta.abort());
|
|
await page.getByLabel('Correo electrónico').fill('prueba@example.test');
|
|
await page.getByRole('button', { name: 'Enviarme el código' }).click();
|
|
await expect(page.getByRole('alert')).toContainText('Conservamos los datos');
|
|
await expect(page.getByLabel('Correo electrónico')).toHaveValue('prueba@example.test');
|
|
await expect(page.getByRole('button', { name: 'Enviarme el código' })).toBeEnabled();
|
|
});
|
|
|
|
test('el código pegado con espacios se acepta y bloquea envíos simultáneos', async ({ page }) => {
|
|
await page.goto('/entrar?paso=codigo&email=prueba%40example.test');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
const campo = page.getByLabel('Código', { exact: true });
|
|
await campo.evaluate((nodo) => {
|
|
const datos = new DataTransfer();
|
|
datos.setData('text/plain', '123 456');
|
|
nodo.dispatchEvent(
|
|
new ClipboardEvent('paste', { clipboardData: datos, bubbles: true, cancelable: true })
|
|
);
|
|
});
|
|
await expect(campo).toHaveValue('123456');
|
|
let liberar!: () => void;
|
|
const espera = new Promise<void>((resolver) => {
|
|
liberar = resolver;
|
|
});
|
|
await page.route('**/entrar?*/verificar', async (ruta) => {
|
|
await espera;
|
|
await ruta.fulfill({
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
type: 'failure',
|
|
status: 400,
|
|
data: stringify({
|
|
paso: 'codigo',
|
|
email: 'prueba@example.test',
|
|
error: 'Código de prueba rechazado.'
|
|
})
|
|
})
|
|
});
|
|
});
|
|
await page.getByRole('button', { name: 'Entrar', exact: true }).click();
|
|
await expect(page.getByRole('button', { name: 'Enviar otro código' })).toBeDisabled();
|
|
await expect(page.getByRole('button', { name: 'Usar otro correo' })).toBeDisabled();
|
|
liberar();
|
|
await expect(page.getByRole('alert')).toContainText('Código de prueba rechazado');
|
|
await expect(campo).toHaveValue('123456');
|
|
});
|
|
|
|
test('el modal y el formulario de contacto tienen etiquetas y focos independientes', async ({
|
|
page
|
|
}) => {
|
|
await page.goto('/contacto');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
await page.getByRole('banner').getByRole('link', { name: 'Entrar', exact: true }).click();
|
|
const dialogo = page.getByRole('dialog');
|
|
await expect(dialogo.getByLabel('Correo electrónico')).toBeFocused();
|
|
await dialogo.locator('label').filter({ hasText: 'Correo electrónico' }).click();
|
|
await expect(dialogo.getByLabel('Correo electrónico')).toBeFocused();
|
|
const duplicados = await page.locator('[id]').evaluateAll((nodos) => {
|
|
const ids = nodos.map((n) => n.id);
|
|
return ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
});
|
|
expect(duplicados).toEqual([]);
|
|
});
|
|
|
|
test('la barra espaciadora acciona el botón enfocado y no cambia la reproducción', async ({
|
|
page
|
|
}) => {
|
|
await page.goto('/albumes/cupido-sin-flechas');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
await page.getByRole('button', { name: 'Reproducir', exact: true }).first().click();
|
|
const barra = page.getByRole('region', { name: 'Reproductor' });
|
|
await expect(barra.getByRole('button', { name: 'Pausar', exact: true })).toBeVisible();
|
|
const tema = page.getByRole('button', { name: /Cambiar al tema/ }).first();
|
|
const nombre = await tema.getAttribute('aria-label');
|
|
await tema.focus();
|
|
await page.keyboard.press('Space');
|
|
await expect(tema).not.toHaveAttribute('aria-label', nombre!);
|
|
await expect(barra.getByRole('button', { name: 'Pausar', exact: true })).toBeVisible();
|
|
await barra.getByRole('button', { name: 'Pausar', exact: true }).focus();
|
|
await page.keyboard.press('Space');
|
|
await expect(barra.getByRole('button', { name: 'Reproducir', exact: true })).toBeVisible();
|
|
});
|
|
|
|
test('cada canción muestra sus versiones y recomendaciones en una fila horizontal', async ({
|
|
page
|
|
}) => {
|
|
await page.goto('/canciones/cuando-tus-manos-me-tocan');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
await page.getByRole('tab', { name: 'Versiones', exact: true }).click();
|
|
const panel = page.getByRole('tabpanel', { name: 'Versiones', exact: true });
|
|
await expect(panel).toBeVisible();
|
|
await expect(panel.getByText('Versión principal', { exact: true })).toBeVisible();
|
|
|
|
const relacionadas = page.getByRole('region', { name: 'Canciones relacionadas' });
|
|
await expect(relacionadas).toBeVisible();
|
|
expect(await relacionadas.locator('.carril > li').count()).toBeGreaterThan(1);
|
|
const medidas = await relacionadas
|
|
.locator('.carril')
|
|
.evaluate((carril) => ({ ancho: carril.clientWidth, contenido: carril.scrollWidth }));
|
|
expect(medidas.contenido).toBeGreaterThan(medidas.ancho);
|
|
await expect(relacionadas.getByText(/^Por |Mismo género|Con |Del mismo/).first()).toBeVisible();
|
|
const motivos = relacionadas.locator('.motivo .etiqueta');
|
|
await expect(motivos.first()).toBeVisible();
|
|
const estilos = await motivos.evaluateAll((elementos) =>
|
|
elementos.map((elemento) => {
|
|
const estilo = getComputedStyle(elemento);
|
|
return {
|
|
color: estilo.color,
|
|
fondo: estilo.backgroundColor,
|
|
tamano: estilo.fontSize
|
|
};
|
|
})
|
|
);
|
|
expect(estilos.every((estilo) => estilo.tamano === '11px')).toBe(true);
|
|
expect(estilos.every((estilo) => estilo.fondo === 'rgba(0, 0, 0, 0)')).toBe(true);
|
|
expect(new Set(estilos.map((estilo) => estilo.color)).size).toBeGreaterThan(1);
|
|
|
|
const tarjetas = relacionadas.locator('.carril > li article');
|
|
const alineacion = await tarjetas.evaluateAll((elementos) =>
|
|
elementos.slice(0, 4).map((tarjeta) => {
|
|
const nota = tarjeta.querySelector('.nota');
|
|
const pie = tarjeta.querySelector('.pie');
|
|
const titulo = tarjeta.querySelector('.titulo');
|
|
const personales = pie?.querySelector('.acciones-personales');
|
|
const iconos = personales?.querySelectorAll('svg');
|
|
const compra = pie?.querySelector('.compra');
|
|
if (!nota || !titulo || !iconos || iconos.length !== 2 || !compra)
|
|
throw new Error('Tarjeta incompleta');
|
|
return {
|
|
nota: Math.round(nota.getBoundingClientRect().top),
|
|
titulo: Math.round(titulo.getBoundingClientRect().left),
|
|
favorito: Math.round(iconos[0].getBoundingClientRect().left),
|
|
lista: Math.round(iconos[1].getBoundingClientRect().left),
|
|
compra: Math.round(compra.getBoundingClientRect().left)
|
|
};
|
|
})
|
|
);
|
|
const posicionesDeNota = alineacion.map((tarjeta) => tarjeta.nota);
|
|
expect(Math.max(...posicionesDeNota) - Math.min(...posicionesDeNota)).toBeLessThanOrEqual(1);
|
|
expect(alineacion.every((tarjeta) => Math.abs(tarjeta.favorito - tarjeta.titulo) <= 1)).toBe(
|
|
true
|
|
);
|
|
expect(
|
|
alineacion.every(
|
|
(tarjeta) => tarjeta.favorito < tarjeta.lista && tarjeta.lista < tarjeta.compra
|
|
)
|
|
).toBe(true);
|
|
});
|
|
|
|
test('favoritos, listas y valoración abren el acceso como popup sin abandonar la canción', async ({
|
|
page
|
|
}) => {
|
|
const ruta = '/canciones/cuando-tus-manos-me-tocan';
|
|
await page.goto(ruta);
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
const ficha = page.locator('header.ficha');
|
|
for (const { nombre, motivo } of [
|
|
{
|
|
nombre: /añadir a favoritos/i,
|
|
motivo:
|
|
'Necesitas entrar para guardar esta canción en Favoritos y recuperarla desde cualquier dispositivo.'
|
|
},
|
|
{
|
|
nombre: /añadir a una lista de reproducción/i,
|
|
motivo:
|
|
'Necesitas entrar para añadir esta canción a una lista de reproducción o crear una nueva.'
|
|
},
|
|
{
|
|
nombre: /inicia sesión para valorar/i,
|
|
motivo:
|
|
'Necesitas entrar para valorar esta canción. Así cada persona puede votar una sola vez.'
|
|
}
|
|
]) {
|
|
await ficha.getByRole('link', { name: nombre }).click();
|
|
const dialogo = page.getByRole('dialog');
|
|
await expect(dialogo).toBeVisible();
|
|
await expect(dialogo.getByText(motivo, { exact: true })).toBeVisible();
|
|
await expect(page).toHaveURL(ruta);
|
|
await page.keyboard.press('Escape');
|
|
await expect(dialogo).toBeHidden();
|
|
}
|
|
});
|