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

248 lines
9.8 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('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('Valoraciones', () => {
test('quien no ha entrado ve solo las estrellas y la puntuación', async ({ page }) => {
await page.goto('/canciones/prometiste');
const bloque = page.getByRole('region', { name: 'Valoración de la canción' });
await expect(bloque).toBeVisible();
await expect(
bloque.getByRole('link', { name: 'Inicia sesión para valorar esta canción' })
).toBeVisible();
await expect(bloque.locator('.estrellas span')).toHaveCount(5);
await expect(bloque.locator('.media')).toBeVisible();
await expect(bloque).not.toContainText(/valoración|de 5|votos?|quitar mi voto/i);
// Sin sesión no se puede votar: no hay estrellas que pulsar.
await expect(bloque.getByRole('radio')).toHaveCount(0);
});
test('las estrellas vuelven al tema tras entrar', async ({ page }) => {
await page.goto('/canciones/prometiste');
await expect(
page
.getByRole('region', { name: 'Valoración de la canción' })
.getByRole('link', { name: 'Inicia sesión para valorar esta canción' })
).toHaveAttribute('href', /volverA=%2Fcanciones%2Fprometiste/);
});
test('un POST sin origen lo para el CSRF, antes de llegar a la acción', async ({ request }) => {
const respuesta = await request.post('/canciones/prometiste?/valorar', {
form: { puntuacion: '5' }
});
expect(respuesta.status()).toBe(403);
});
test('sin sesión, la acción de valorar se rechaza', async ({ request, baseURL }) => {
// Con el origen correcto la petición llega a la acción, así que lo que se
// comprueba aquí es su guardia y no la del framework. La interfaz no
// enseña estrellas sin sesión, pero el formulario se puede enviar a mano.
//
// La respuesta es el resultado de la acción en JSON, no la página: el
// código de `fail` viaja dentro, y el de la respuesta HTTP es 200.
const respuesta = await request.post('/canciones/prometiste?/valorar', {
headers: { origin: baseURL ?? '' },
form: { puntuacion: '5' }
});
const resultado = JSON.parse(await respuesta.text());
expect(resultado.type).toBe('failure');
expect(resultado.status).toBe(401);
});
test('una puntuación fuera de rango tampoco pasa', async ({ request, baseURL }) => {
// Sin sesión gana la guardia de la cuenta; que el rango se comprueba está
// cubierto aparte, en las pruebas de `aPuntuacion`.
const respuesta = await request.post('/canciones/prometiste?/valorar', {
headers: { origin: baseURL ?? '' },
form: { puntuacion: '9' }
});
expect(JSON.parse(await respuesta.text()).type).toBe('failure');
});
});
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');
// 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.