ci / build-and-test (push) Canceled after 0s
Replace the @astrojs/sitemap integration with two handwritten endpoints under src/pages/ so we can attach a Google News <news:news> block to each aviso detail URL. The integration already emitted the news namespace but never the per-URL blocks, so the project was not eligible for Google News indexing. - src/pages/sitemap-index.xml.ts: single-child sitemapindex pointing at /sitemap-0.xml. - src/pages/sitemap-0.xml.ts: home + huella + one entry per aviso from getAvisosAmbientales. Each aviso carries <news:news> with publication name (fuente), language ca, publication_date (ISO), title and keywords (temas joined). - astro.config.mjs: drop the @astrojs/sitemap integration; remove the dependency from package.json and pnpm-lock.yaml. - e2e: add a test asserting the news namespace and at least one <news:news> block with publication_date and language. 22/22 e2e pass; check and eslint clean.
153 lines
7.0 KiB
TypeScript
153 lines
7.0 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import AxeBuilder from '@axe-core/playwright';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
|
|
const dist = (name: string) => join(process.cwd(), 'dist', name);
|
|
|
|
test.describe('site smoke', () => {
|
|
test('home renders and reports no axe violations @ desktop', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
await expect(page).toHaveTitle(/Avisos ambientals d'Eivissa i Formentera/);
|
|
await expect(page.locator('h1#page-title')).toBeVisible();
|
|
await expect(page.locator('main#main-content')).toBeVisible();
|
|
await expect(page.locator('text=Resum d\'avisos per municipi')).toBeVisible();
|
|
|
|
const results = await new AxeBuilder({ page })
|
|
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
|
|
.disableRules(['region']) // decorative map region is intentional
|
|
.analyze();
|
|
expect(results.violations, JSON.stringify(results.violations, null, 2)).toEqual([]);
|
|
});
|
|
|
|
test('skip-link is the first interactive element and jumps to main', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.keyboard.press('Tab');
|
|
const focused = await page.evaluate(() => document.activeElement?.textContent?.trim());
|
|
expect(focused).toBe('Saltar al contingut');
|
|
await page.keyboard.press('Enter');
|
|
await expect(page.locator('main#main-content')).toBeFocused();
|
|
});
|
|
|
|
test('filter pills toggle aria-pressed and update the live counter', async ({ page }) => {
|
|
await page.goto('/');
|
|
const municipioGroup = page.locator('[data-filter-type="municipio"]');
|
|
const allPills = municipioGroup.locator('.pill');
|
|
const firstPill = allPills.first();
|
|
const targetValue = await municipioGroup
|
|
.locator('.pill:not(:first-child)')
|
|
.first()
|
|
.getAttribute('data-value');
|
|
expect(targetValue).toBeTruthy();
|
|
const targetPill = municipioGroup.locator(`.pill[data-value="${targetValue}"]`);
|
|
|
|
await expect(firstPill).toHaveAttribute('aria-pressed', 'true');
|
|
await targetPill.click();
|
|
await expect(firstPill).toHaveAttribute('aria-pressed', 'false');
|
|
await expect(targetPill).toHaveAttribute('aria-pressed', 'true');
|
|
|
|
const counter = page.locator('#filters-count');
|
|
await expect(counter).toContainText(/avís/i);
|
|
});
|
|
|
|
test('404 page is noindex and reachable', async ({ page }) => {
|
|
const response = await page.goto('/no-existeix-aqui');
|
|
expect(response?.status()).toBe(404);
|
|
const robots = await page.locator('meta[name="robots"]').getAttribute('content');
|
|
expect(robots).toContain('noindex');
|
|
await expect(page.locator('h1#not-found-title')).toBeVisible();
|
|
});
|
|
|
|
test('JSON-LD on the home parses and exposes Organization + ItemList', async ({ page }) => {
|
|
await page.goto('/');
|
|
const payload = await page.locator('head script[type="application/ld+json"]').first().textContent();
|
|
expect(payload).toBeTruthy();
|
|
const data = JSON.parse(payload ?? '{}');
|
|
const types = (data['@graph'] as Array<{ '@type': string }>).map((n) => n['@type']);
|
|
expect(types).toEqual(expect.arrayContaining(['Organization', 'WebSite', 'ItemList']));
|
|
});
|
|
|
|
test('robots.txt built from Astro.site and points at the sitemap', async ({ request }) => {
|
|
const res = await request.get('/robots.txt');
|
|
expect(res.status()).toBe(200);
|
|
const body = await res.text();
|
|
expect(body).toMatch(/User-agent: \*/);
|
|
expect(body).toMatch(/Sitemap: https:\/\/www\.eivissaenvironmentalwatch\.es\/sitemap-index\.xml/);
|
|
});
|
|
|
|
test('sitemap lists the two real pages and not the 404', async ({ request }) => {
|
|
const res = await request.get('/sitemap-index.xml');
|
|
expect(res.status()).toBe(200);
|
|
const index = await res.text();
|
|
const sub = await request.get(/<loc>(.*?)<\/loc>/.exec(index)?.[1] ?? '/sitemap-0.xml');
|
|
const sitemap = await sub.text();
|
|
expect(sitemap).toContain('https://www.eivissaenvironmentalwatch.es/');
|
|
expect(sitemap).toContain('https://www.eivissaenvironmentalwatch.es/huella/');
|
|
expect(sitemap).not.toContain('/404/');
|
|
});
|
|
|
|
test('aviso detail page renders and exposes NewsArticle JSON-LD', async ({ request, page }) => {
|
|
const index = await request.get('/sitemap-0.xml');
|
|
const sitemap = await index.text();
|
|
const avisPath = /\/avis\/[a-z0-9-]+\//.exec(sitemap)?.[0];
|
|
expect(avisPath, 'sitemap should list at least one aviso detail page').toBeTruthy();
|
|
|
|
const response = await request.get(avisPath!);
|
|
expect(response.status()).toBe(200);
|
|
|
|
await page.goto(avisPath!);
|
|
await expect(page.locator('h1#aviso-title')).toBeVisible();
|
|
await expect(page.locator('aside.aviso__source a[rel="noopener noreferrer"][target="_blank"]')).toBeVisible();
|
|
|
|
const payload = await page.locator('head script[type="application/ld+json"]').first().textContent();
|
|
expect(payload).toBeTruthy();
|
|
const data = JSON.parse(payload ?? '{}');
|
|
const graph = data['@graph'] as Array<Record<string, unknown>>;
|
|
const types = graph.map((n) => n['@type']);
|
|
expect(types).toEqual(expect.arrayContaining(['Organization', 'WebSite', 'NewsArticle']));
|
|
const article = graph.find((n) => n['@type'] === 'NewsArticle');
|
|
expect(article, 'expected a NewsArticle node in JSON-LD').toBeTruthy();
|
|
expect(article?.['isAccessibleForFree']).toBe(true);
|
|
expect(article?.['inLanguage']).toBe('ca-ES');
|
|
expect(article?.['mainEntityOfPage']).toBeTruthy();
|
|
|
|
const ogImage = await page.locator('meta[property="og:image"]').first().getAttribute('content');
|
|
expect(ogImage).toMatch(/\/og\/avis\/[a-z0-9-]+\.png$/);
|
|
const ogUrl = ogImage!.replace('https://www.eivissaenvironmentalwatch.es', '');
|
|
const ogRes = await request.get(ogUrl);
|
|
expect(ogRes.status(), ogUrl).toBe(200);
|
|
});
|
|
|
|
test('sitemap includes every aviso detail page', async ({ request }) => {
|
|
const res = await request.get('/sitemap-0.xml');
|
|
const sitemap = await res.text();
|
|
const avisPaths = sitemap.match(/\/avis\/[a-z0-9-]+\//g) ?? [];
|
|
expect(avisPaths.length).toBeGreaterThan(0);
|
|
for (const p of avisPaths) {
|
|
const r = await request.get(p);
|
|
expect(r.status(), p).toBe(200);
|
|
}
|
|
});
|
|
|
|
test('news sitemap declares Google News namespace and per-aviso news block', async ({ request }) => {
|
|
const res = await request.get('/sitemap-0.xml');
|
|
const sitemap = await res.text();
|
|
expect(sitemap).toContain('xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"');
|
|
const newsBlocks = sitemap.match(/<news:news>/g) ?? [];
|
|
expect(newsBlocks.length).toBeGreaterThan(0);
|
|
expect(sitemap).toMatch(/<news:publication_date>[^<]+<\/news:publication_date>/);
|
|
expect(sitemap).toMatch(/<news:language>ca<\/news:language>/);
|
|
});
|
|
});
|
|
|
|
test.describe('build artifacts', () => {
|
|
test('og PNG exists with the right dimensions', async () => {
|
|
const buf = await readFile(dist('og-default.png'));
|
|
expect(buf.subarray(0, 8)).toEqual(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
|
|
const width = buf.readUInt32BE(16);
|
|
const height = buf.readUInt32BE(20);
|
|
expect({ width, height }).toEqual({ width: 1200, height: 630 });
|
|
});
|
|
});
|