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

207 lines
7.2 KiB

import { expect, test } from '@playwright/test';
test.describe('Feed del blog', () => {
test('se sirve como RSS y lo anuncia toda la web', async ({ page, request }) => {
const respuesta = await request.get('/blog/rss.xml');
expect(respuesta.status()).toBe(200);
expect(respuesta.headers()['content-type']).toContain('application/rss+xml');
// Sin el `<link rel="alternate">` un lector no lo encuentra solo.
await page.goto('/');
await expect(page.locator('link[type="application/rss+xml"]')).toHaveAttribute(
'href',
'/blog/rss.xml'
);
});
test('lleva todas las entradas, de la más reciente primero', async ({ request }) => {
const xml = await (await request.get('/blog/rss.xml')).text();
const fechas = [...xml.matchAll(/<pubDate>(.*?)<\/pubDate>/g)].map((m) =>
new Date(m[1]).getTime()
);
expect(fechas.length).toBeGreaterThan(1);
expect(fechas).toEqual([...fechas].sort((a, b) => b - a));
});
test('las URLs son absolutas', async ({ request }) => {
// Un feed se lee fuera del sitio: una ruta relativa no lleva a ninguna
// parte.
const xml = await (await request.get('/blog/rss.xml')).text();
const enlaces = [...xml.matchAll(/<link>(.*?)<\/link>/g)].map((m) => m[1]);
expect(enlaces.length).toBeGreaterThan(0);
expect(enlaces.every((e) => e.startsWith('https://'))).toBe(true);
});
});
test.describe('Mapa del sitio', () => {
test('se sirve como XML y lo anuncia robots.txt', async ({ request }) => {
const respuesta = await request.get('/sitemap.xml');
expect(respuesta.status()).toBe(200);
expect(respuesta.headers()['content-type']).toContain('xml');
const robots = await (await request.get('/robots.txt')).text();
expect(robots).toContain('Sitemap: https://senzapaura.es/sitemap.xml');
// Y lo que no es contenido, fuera del rastreo.
for (const privada of ['/cuenta', '/carrito', '/descargas', '/acceso']) {
expect(robots).toContain(`Disallow: ${privada}`);
}
});
test('incluye el catálogo entero', async ({ request }) => {
const xml = await (await request.get('/sitemap.xml')).text();
// Una muestra de cada tipo: si algo se cayera del mapa, sería por tipo.
expect(xml).toContain('/canciones/dorina');
expect(xml).toContain('/musica/baladas-romanticas');
expect(xml).toContain('/albumes/singles-baladas-romanticas');
expect(xml).toContain('/generos/bachata');
expect(xml).toContain('/blog/lo-que-no-cabe-en-una-cancion');
expect(xml).toContain('/etiquetas/desamor');
});
test('no publica lo que depende de quién mira', async ({ request }) => {
const xml = await (await request.get('/sitemap.xml')).text();
for (const privada of [
'/cuenta',
'/carrito',
'/favoritos',
'/entrar',
'/descargas',
'/admin'
]) {
expect(xml).not.toContain(`<loc>https://senzapaura.es${privada}`);
}
});
});
test.describe('Imagen al compartir', () => {
test('una página sin portada propia usa la de reserva', async ({ page }) => {
await page.goto('/taller');
await expect(page.locator('meta[property="og:image"]')).toHaveAttribute(
'content',
'https://senzapaura.es/og.jpg'
);
});
test('un tema comparte su propia portada', async ({ page }) => {
await page.goto('/canciones/como-si-nada');
const contenido = await page.locator('meta[property="og:image"]').getAttribute('content');
expect(contenido).not.toContain('/og.jpg');
expect(contenido).toContain('/images/');
});
test('la imagen de reserva existe y tiene la proporción de una tarjeta', async ({ request }) => {
const respuesta = await request.get('/og.jpg');
expect(respuesta.status()).toBe(200);
expect(respuesta.headers()['content-type']).toContain('image/jpeg');
});
});
test.describe('Legal', () => {
test('las cinco páginas están y se enlazan desde el pie', async ({ page }) => {
await page.goto('/');
const pie = page.getByRole('navigation', { name: 'Legal' });
for (const titulo of [
'Información legal',
'Condiciones de uso',
'Política de privacidad',
'Política de cookies',
'Condiciones de venta'
]) {
await expect(pie.getByRole('link', { name: titulo })).toBeVisible();
}
});
test('cada texto legal dice cuándo se revisó', async ({ page }) => {
// Un aviso legal sin fecha no sirve: quien lo lee necesita saber a qué
// versión se está acogiendo.
await page.goto('/legal/privacidad');
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Política de privacidad');
await expect(page.getByText(/Última revisión/)).toBeVisible();
});
test('desde un texto legal se llega a los demás', async ({ page }) => {
await page.goto('/legal/cookies');
const lateral = page.getByRole('navigation', { name: 'Textos legales' });
await expect(lateral.getByRole('link', { name: 'Política de cookies' })).toHaveAttribute(
'aria-current',
'page'
);
await lateral.getByRole('link', { name: 'Condiciones de venta' }).click();
await expect(page).toHaveURL('/legal/condiciones-de-venta');
});
/*
* El índice de apartados sale del Markdown en bruto y las anclas las pone un
* plugin al compilarlo: son dos caminos distintos que tienen que dar la misma
* cadena. Si se separan, los enlaces siguen pintándose y no llevan a ningún
* sitio, que es la clase de avería que no da error y no ve nadie.
*/
for (const slug of [
'aviso-legal',
'condiciones-de-uso',
'privacidad',
'cookies',
'condiciones-de-venta'
]) {
test(`el índice de /legal/${slug} apunta a apartados que existen`, async ({ page }) => {
await page.goto(`/legal/${slug}`);
const indice = page.getByRole('navigation', { name: 'Apartados de esta página' });
const destinos = await indice
.getByRole('link')
.evaluateAll((enlaces) => enlaces.map((e) => e.getAttribute('href') ?? ''));
expect(destinos.length).toBeGreaterThan(2);
for (const destino of destinos) {
await expect(page.locator(`h2${destino}`)).toHaveCount(1);
}
});
}
test('pinchar un apartado lo deja visible bajo la cabecera', async ({ page }) => {
// La cabecera es pegajosa: sin `scroll-margin-top` el salto deja el título
// justo debajo de ella y parece que no ha pasado nada.
await page.goto('/legal/condiciones-de-venta');
const indice = page.getByRole('navigation', { name: 'Apartados de esta página' });
await indice.getByRole('link', { name: 'Derecho de desistimiento' }).click();
const apartado = page.locator('h2#derecho-de-desistimiento');
await expect(apartado).toBeInViewport();
const debajo = await apartado.evaluate((h) => {
const cabecera = document.querySelector('header')!.getBoundingClientRect().bottom;
return h.getBoundingClientRect().top >= cabecera;
});
expect(debajo).toBe(true);
});
});
test.describe('Sin terceros', () => {
test('cargar la web no contacta con ningún dominio ajeno', async ({ page }) => {
// Las tipografías se sirven desde aquí. Cargarlas de Google mandaba la IP
// de cada visitante a un tercero en cada página, sin pedirlo.
const ajenos = new Set<string>();
page.on('request', (r) => {
const host = new URL(r.url()).host;
if (!host.startsWith('localhost') && !host.startsWith('127.0.0.1')) ajenos.add(host);
});
for (const ruta of ['/', '/musica', '/canciones/dorina', '/entrar']) {
await page.goto(ruta);
}
expect([...ajenos]).toEqual([]);
});
});

Powered by TurnKey Linux.