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.
senzapaura_es/e2e/blog.e2e.ts

305 lines
12 KiB

import { expect, test } from '@playwright/test';
test.describe('Blog', () => {
test('está en la navegación principal', async ({ page }) => {
await page.goto('/');
await page
.getByRole('navigation', { name: 'Principal' })
.getByRole('link', { name: 'Blog' })
.click();
await expect(page).toHaveURL('/blog');
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Blog');
});
test('las entradas van de la más reciente a la más antigua', async ({ page }) => {
await page.goto('/blog');
const fechas = await page
.locator('time')
.evaluateAll((ts) => ts.map((t) => t.getAttribute('datetime') ?? ''));
expect(fechas.length).toBeGreaterThan(1);
expect(fechas).toEqual([...fechas].sort().reverse());
});
test('buscar y cambiar de vista comparten el mismo control', async ({ page }) => {
await page.goto('/blog');
await page.locator('html[data-hidratado]').waitFor();
await page.getByRole('searchbox', { name: 'Buscar una entrada' }).fill('no cabe');
await expect(page.getByRole('status')).toContainText('1 entrada');
await expect(page.getByRole('link', { name: 'Lo que no cabe en una canción' })).toBeVisible();
const tabla = page.getByRole('button', { name: 'Tabla' });
await tabla.click();
await expect(tabla).toHaveAttribute('aria-pressed', 'true');
await expect(page.getByRole('table')).toBeVisible();
});
test('las etiquetas no tienen una opción ficticia y admiten selección múltiple', async ({
page
}) => {
await page.goto('/blog');
await page.locator('html[data-hidratado]').waitFor();
const grupo = page.getByRole('group', { name: 'Filtrar por etiqueta' });
await expect(grupo.getByRole('button', { name: 'Todas', exact: true })).toHaveCount(0);
const colores = await grupo
.getByRole('button')
.evaluateAll((botones) => botones.map((boton) => getComputedStyle(boton).color));
expect(new Set(colores).size).toBeGreaterThan(1);
const primera = grupo.getByRole('button').nth(0);
const segunda = grupo.getByRole('button').nth(1);
await primera.click();
await segunda.click();
await expect(primera).toHaveAttribute('aria-pressed', 'true');
await expect(segunda).toHaveAttribute('aria-pressed', 'true');
await primera.click();
await expect(primera).toHaveAttribute('aria-pressed', 'false');
await expect(segunda).toHaveAttribute('aria-pressed', 'true');
});
test('Atrás y Adelante restauran la presentación y las etiquetas', async ({ page }) => {
await page.goto('/blog');
await page.locator('html[data-hidratado]').waitFor();
const tabla = page.getByRole('button', { name: 'Tabla' });
const tarjetas = page.getByRole('button', { name: 'Tarjetas' });
const primeraEtiqueta = page
.getByRole('group', { name: 'Filtrar por etiqueta' })
.getByRole('button')
.first();
await tabla.click();
await expect(page).toHaveURL('/blog?presentacion=tabla');
await expect(page.getByRole('table')).toBeVisible();
await primeraEtiqueta.click();
await expect(page).toHaveURL(/\/blog\?presentacion=tabla&etiqueta=.+/);
await expect(primeraEtiqueta).toHaveAttribute('aria-pressed', 'true');
await page.goBack();
await expect(page).toHaveURL('/blog?presentacion=tabla');
await expect(primeraEtiqueta).toHaveAttribute('aria-pressed', 'false');
await expect(tabla).toHaveAttribute('aria-pressed', 'true');
await page.goBack();
await expect(page).toHaveURL('/blog');
await expect(tarjetas).toHaveAttribute('aria-pressed', 'true');
await expect(page.locator('ul.entradas')).toBeVisible();
await page.goForward();
await expect(page).toHaveURL('/blog?presentacion=tabla');
await expect(tabla).toHaveAttribute('aria-pressed', 'true');
await page.goForward();
await expect(page).toHaveURL(/\/blog\?presentacion=tabla&etiqueta=.+/);
await expect(primeraEtiqueta).toHaveAttribute('aria-pressed', 'true');
});
test('Atrás recupera la búsqueda después de abrir una entrada', async ({ page }) => {
await page.goto('/blog');
const buscador = page.getByRole('searchbox', { name: 'Buscar una entrada' });
await buscador.fill('no cabe');
await expect(page.getByRole('status')).toContainText('1 entrada');
await page.getByRole('link', { name: 'Lo que no cabe en una canción' }).click();
await expect(page).toHaveURL('/blog/lo-que-no-cabe-en-una-cancion');
await page.goBack();
await expect(page).toHaveURL('/blog');
await expect(buscador).toHaveValue('no cabe');
await expect(page.getByRole('status')).toContainText('1 entrada');
});
test('una entrada se lee entera y encadena con la anterior', async ({ page }) => {
await page.goto('/blog');
await page.getByRole('link', { name: 'Escribir en un estilo que no es el tuyo' }).click();
await expect(page).toHaveURL('/blog/escribir-en-un-estilo-ajeno');
await expect(
page.getByRole('heading', { level: 2, name: 'No es el ritmo, es el registro' })
).toBeVisible();
await expect(page.locator('.documento .bloque--texto')).toHaveCount(1);
await page.getByRole('link', { name: /Anterior/ }).click();
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
'Lo que no cabe en una canción'
);
});
test('una entrada no conserva enlaces a canciones eliminadas', async ({ page }) => {
await page.goto('/blog/lo-que-no-cabe-en-una-cancion');
await expect(page.getByRole('heading', { name: 'De lo que habla' })).toHaveCount(0);
await expect(page.getByRole('heading', { name: 'Como si nada' })).toHaveCount(0);
});
test('el detalle distingue una portada real de la reserva del listado', async ({ page }) => {
await page.setViewportSize({ width: 1000, height: 900 });
await page.goto('/blog/lo-que-no-cabe-en-una-cancion');
const titulo = page.getByRole('heading', { level: 1 });
await expect(titulo).toHaveText('Lo que no cabe en una canción');
await expect(page.locator('article img.portada-entrada')).toHaveCount(0);
await expect(page.locator('article')).not.toContainText('Sin portada');
await expect(page.getByLabel('Datos del artículo')).toContainText('Publicado');
await expect(page.getByLabel('Datos del artículo')).toContainText('Lectura');
const indice = page.getByRole('complementary', { name: 'Más en el cuaderno' });
await expect(indice).toBeVisible();
await expect(
indice.getByRole('link', { name: /Lo que no cabe en una canción/ })
).toHaveAttribute('aria-current', 'page');
expect(await indice.getByRole('link').count()).toBeGreaterThan(1);
const estructura = await page.evaluate(() => {
const cabecera = document.querySelector('.intro')?.getBoundingClientRect();
const lateral = document.querySelector('.indice-blog')?.getBoundingClientRect();
const interior = document.querySelector('.indice-interior');
return {
diferenciaVertical: Math.abs((cabecera?.top ?? 0) - (lateral?.top ?? 0)),
posicion: interior ? getComputedStyle(interior).position : ''
};
});
expect(estructura.diferenciaVertical).toBeLessThan(1);
expect(estructura.posicion).toBe('sticky');
const medidas = await titulo.evaluate((elemento) => {
const caja = elemento.getBoundingClientRect();
return {
ancho: caja.width,
alto: caja.height,
tamano: parseFloat(getComputedStyle(elemento).fontSize)
};
});
expect(medidas.ancho).toBeGreaterThan(540);
expect(medidas.ancho).toBeLessThan(600);
expect(medidas.alto).toBeGreaterThan(100);
expect(medidas.alto).toBeLessThan(125);
expect(medidas.tamano).toBeGreaterThanOrEqual(56);
const cuerpo = page.locator('.contenido .prosa').first();
await expect(cuerpo).toBeVisible();
const lectura = await cuerpo.evaluate((elemento) => {
const caja = elemento.getBoundingClientRect();
return { ancho: caja.width, tamano: parseFloat(getComputedStyle(elemento).fontSize) };
});
expect(lectura.ancho).toBeGreaterThanOrEqual(570);
expect(lectura.ancho).toBeLessThanOrEqual(680);
expect(lectura.tamano).toBeGreaterThanOrEqual(18);
const primerParrafo = cuerpo.locator('p').first();
const capitular = await primerParrafo.evaluate((elemento) => {
const estilo = getComputedStyle(elemento, '::first-letter');
return { tamano: parseFloat(estilo.fontSize), flotante: estilo.float };
});
expect(capitular.tamano).toBeGreaterThan(70);
expect(capitular.flotante).toBe('left');
});
test('las migas conservan el eje izquierdo de la cabecera en móvil', async ({ page }) => {
await page.setViewportSize({ width: 336, height: 800 });
await page.goto('/blog/lo-que-no-cabe-en-una-cancion');
const alineacion = await page.evaluate(() => {
const marca = document.querySelector('header .marca')!.getBoundingClientRect();
const ruta = document
.querySelector<HTMLElement>("nav[aria-label='Ruta']")!
.getBoundingClientRect();
return {
diferenciaIzquierda: Math.abs(marca.left - ruta.left),
espacioDerecho: innerWidth - ruta.right,
ancho: ruta.width
};
});
expect(alineacion.diferenciaIzquierda).toBeLessThanOrEqual(1);
expect(alineacion.espacioDerecho).toBeGreaterThanOrEqual(16);
expect(alineacion.ancho).toBeGreaterThanOrEqual(300);
});
test('las etiquetas de una entrada filtran la música relacionada', async ({ page }) => {
await page.goto('/blog/lo-que-no-cabe-en-una-cancion');
await page.getByRole('link', { name: 'Desamor', exact: true }).click();
await expect(page).toHaveURL('/musica?etiqueta=desamor');
});
test('una entrada que no existe da 404', async ({ page }) => {
const respuesta = await page.goto('/blog/no-existe');
expect(respuesta?.status()).toBe(404);
});
});
test.describe('Me gusta', () => {
test('muestra el pulgar y el contador, sin estrellas', async ({ page }) => {
await page.goto('/canciones/prometiste');
const bloque = page.locator('header.ficha .me-gusta');
await expect(
bloque.getByRole('link', { name: /Me gusta: \d+ likes?\. Inicia sesión/ })
).toBeVisible();
await expect(bloque.locator('.contador')).toHaveText(/^\d+$/);
await expect(page.locator('.estrellas')).toHaveCount(0);
});
test('conserva la canción como destino al entrar', async ({ page }) => {
await page.goto('/canciones/prometiste');
await expect(page.locator('header.ficha .me-gusta a')).toHaveAttribute(
'href',
/volverA=%2Fcanciones%2Fprometiste/
);
});
test('rechaza un POST sin origen', async ({ request }) => {
const r = await request.post('/canciones/prometiste?/meGusta', { form: { activo: '1' } });
expect(r.status()).toBe(403);
});
test('rechaza likes sin sesión', async ({ request, baseURL }) => {
const r = await request.post('/canciones/prometiste?/meGusta', {
headers: { origin: baseURL ?? '', accept: 'application/json', 'x-sveltekit-action': 'true' },
form: { activo: '1' }
});
expect(JSON.parse(await r.text())).toMatchObject({ type: 'failure', status: 401 });
});
});
test.describe('Favoritos', () => {
test('quien no ha entrado ve el corazón y abre el acceso en la ficha', async ({ page }) => {
await page.goto('/canciones/prometiste');
await expect(page.locator('html')).toHaveAttribute('data-hidratado', '');
// No se esconde: la interfaz no cambia de forma según quién mira.
const enlace = page.locator('header.ficha').getByRole('link', {
name: 'Entra con tu cuenta para añadir a favoritos'
});
await expect(enlace).toBeVisible();
await expect(enlace).toHaveAttribute('href', /volverA=%2Fcanciones%2Fprometiste/);
await enlace.click();
await expect(page.getByRole('dialog', { name: 'Entrar o crear cuenta' })).toBeVisible();
await expect(page).toHaveURL(/\/canciones\/prometiste$/);
});
test('la lista invita a entrar si no hay sesión', async ({ page }) => {
await page.goto('/favoritos');
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Favoritos');
await expect(page.getByRole('link', { name: 'Entra con tu cuenta' })).toBeVisible();
});
test('sin sesión, marcar un favorito se rechaza', async ({ request, baseURL }) => {
const respuesta = await request.post('/favoritos?/marcar', {
headers: { origin: baseURL ?? '' },
form: { cancion: 'prometiste', volverA: '/favoritos' }
});
const resultado = JSON.parse(await respuesta.text());
expect(resultado.type).toBe('failure');
expect(resultado.status).toBe(401);
});
test('un tema inventado no se puede guardar', async ({ request, baseURL }) => {
const respuesta = await request.post('/favoritos?/marcar', {
headers: { origin: baseURL ?? '' },
form: { cancion: 'no-existe', volverA: '/favoritos' }
});
expect(JSON.parse(await respuesta.text()).type).toBe('failure');
});
});

Powered by TurnKey Linux.