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

193 lines
8.5 KiB

import { test, expect } from '@playwright/test';
import { randomUUID, randomBytes, createHash } from 'node:crypto';
import { readFileSync, mkdirSync } from 'node:fs';
import { parseEnv } from 'node:util';
import pg from 'pg';
const env = parseEnv(readFileSync('.env.estudio.local', 'utf8'));
const url = new URL(env.DATABASE_URL);
if (
!/^estudio_pruebas_/.test(env.ESTUDIO_ESQUEMA) ||
!url.searchParams.get('options')?.includes(`search_path=${env.ESTUDIO_ESQUEMA},`)
)
throw Error('Estas pruebas requieren un esquema aislado.');
const db = new pg.Pool({ connectionString: env.DATABASE_URL, max: 2 });
const token = randomBytes(32).toString('hex');
const admin = 'estudio-e2e-admin';
test.beforeAll(async () => {
expect((await db.query('SELECT current_schema() AS nombre')).rows[0].nombre).toBe(
env.ESTUDIO_ESQUEMA
);
await db.query(
"INSERT INTO usuario(id,email,nombre,rol) VALUES($1,'estudio@example.test','Cuenta de pruebas','admin') ON CONFLICT(id) DO NOTHING",
[admin]
);
await db.query(
"INSERT INTO sesion(id,usuario_id,expira_en) VALUES($1,$2,now()+interval '1 day')",
[createHash('sha256').update(token).digest('hex'), admin]
);
mkdirSync('docs/assets/estudio', { recursive: true });
});
test.afterAll(async () => {
await db.query('DELETE FROM sesion WHERE id=$1', [
createHash('sha256').update(token).digest('hex')
]);
await db.end();
});
test.beforeEach(async ({ context }) => {
await context.addCookies([
{
name: 'sesion',
value: token,
domain: '127.0.0.1',
path: '/',
httpOnly: true,
sameSite: 'Lax'
}
]);
});
test('portada y navegación nueva', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: 'Administración', exact: true })).toBeVisible();
await expect(page.getByRole('navigation', { name: 'Administración' })).toBeVisible();
await page.screenshot({ path: 'docs/assets/estudio/inicio.png', fullPage: true });
for (const path of [
'/canciones',
'lanzamientos',
'personas',
'estilos',
'blog',
'paginas',
'legales',
'capitulos',
'generos',
'glosario',
'prompts',
'modelos',
'vocabularios',
'redirecciones',
'medios',
'actividad',
'operacion/pedidos',
'operacion/cuentas',
'operacion/suscripciones'
]) {
const response = await page.goto(path.startsWith('/') ? path : `/${path}`);
expect(response?.status(), path).toBe(200);
await expect(page.locator('h1')).toBeVisible();
}
});
test('crear canción, conservar el borrador y añadir artista sin salir', async ({ page }) => {
const titulo = `Canción de prueba ${randomUUID().slice(0, 8)}`;
await page.goto('/canciones/nuevo');
await page.getByLabel('Nombre o título').fill(titulo);
await page.getByRole('button', { name: 'Crear borrador' }).click();
await expect(page.getByRole('heading', { name: titulo, exact: true, level: 1 })).toBeVisible();
const cancionUrl = page.url();
await page.getByRole('link', { name: 'Datos y contenido', exact: true }).click();
const inspiracion = page.getByRole('group', { name: 'Inspiración de la canción' });
await expect(inspiracion).toBeVisible();
await inspiracion.getByRole('button', { name: 'Texto', exact: false }).click();
await inspiracion.getByLabel(/^Texto/).fill('Una nota que debe conservarse.');
await page.getByRole('button', { name: 'Guardar borrador', exact: true }).click();
await expect(page.getByRole('status')).toContainText('guardado');
await page.getByRole('link', { name: 'Grabaciones', exact: true }).click();
await page.getByRole('link', { name: 'Abrir →', exact: true }).click();
await expect(page).toHaveURL(/\/grabaciones\/[^/?]+/);
await page.getByRole('link', { name: 'Créditos', exact: true }).click();
const bloque = page
.locator('section.bloque')
.filter({ has: page.getByRole('heading', { name: 'Intérpretes principales', exact: true }) });
await bloque.getByRole('button', { name: 'Crear', exact: false }).click();
const dialogo = page.getByRole('dialog');
await expect(dialogo).toBeVisible();
const artista = `Intérprete ${randomUUID().slice(0, 8)}`;
await dialogo.getByLabel('Nombre', { exact: true }).fill(artista);
await dialogo.getByRole('button', { name: 'Crear y seleccionar' }).click();
await expect(dialogo).not.toBeVisible();
await expect(bloque.getByRole('link', { name: artista })).toBeVisible();
await page.getByRole('button', { name: 'Guardar borrador', exact: true }).click();
await expect(page.getByRole('status')).toContainText('guardado');
await page.goto(cancionUrl);
await page.getByRole('link', { name: 'Datos y contenido', exact: true }).click();
await expect(
page.getByRole('group', { name: 'Inspiración de la canción' }).getByLabel(/^Texto/)
).toHaveValue('Una nota que debe conservarse.');
await page.screenshot({ path: 'docs/assets/estudio/ficha.png', fullPage: true });
});
test('el blog permite componer y reordenar cinco tipos de sección', async ({ page }) => {
const titulo = `Entrada por bloques ${randomUUID().slice(0, 8)}`;
await page.goto('/blog/nuevo');
await page.getByLabel('Nombre o título').fill(titulo);
await page.getByRole('button', { name: 'Crear borrador' }).click();
const editor = page.getByRole('group', { name: 'Secciones' });
for (const tipo of ['Texto', 'Código', 'Vídeo', 'Imagen', 'Sonido'])
await editor.getByRole('button', { name: tipo, exact: false }).click();
await expect(editor.locator('li.seccion')).toHaveCount(5);
await editor.getByLabel(/^Texto/).fill('Primer bloque del artículo.');
await editor.getByLabel('Código', { exact: true }).fill('const cancion = true;');
await editor.getByLabel('Lenguaje', { exact: false }).fill('typescript');
await editor.getByLabel('Dirección del vídeo').fill('https://youtu.be/dQw4w9WgXcQ');
await editor.getByLabel('Dirección de la imagen').fill('/imagenes/ejemplo.jpg');
await editor.getByLabel('Descripción accesible').fill('Imagen de ejemplo');
await editor.getByLabel('Dirección del sonido').fill('https://media.example/maqueta.mp3');
await editor.getByRole('button', { name: 'Subir sección 2' }).click();
await expect(editor.locator('li.seccion').first().getByLabel('Tipo de sección')).toHaveValue(
'codigo'
);
await page.screenshot({ path: 'docs/assets/estudio/editor-secciones-blog.png', fullPage: true });
await page.getByRole('button', { name: 'Guardar borrador', exact: true }).click();
await expect(page.getByRole('status')).toContainText('guardado');
await page.reload();
await expect(editor.locator('li.seccion')).toHaveCount(5);
await expect(editor.getByLabel('Código', { exact: true })).toHaveValue('const cancion = true;');
});
test('ficha publicada: guardar un borrador no cambia lo visible', async ({ page }) => {
const {
rows: [d]
} = await db.query(
"SELECT * FROM estudio_documento WHERE tipo='paginas' AND estado='publicado' LIMIT 1"
);
expect(d).toBeTruthy();
const antes = JSON.stringify(d.publicado);
await page.goto(`/paginas/${d.id}?tab=datos`);
await page.getByLabel('Descripción breve').fill(`Borrador de prueba ${Date.now()}`);
await page.getByRole('button', { name: 'Guardar borrador', exact: true }).first().click();
await expect(page.getByRole('status')).toBeVisible();
const {
rows: [despues]
} = await db.query('SELECT publicado,borrador FROM estudio_documento WHERE id=$1', [d.id]);
expect(JSON.stringify(despues.publicado)).toBe(antes);
expect(despues.borrador).not.toEqual(despues.publicado);
});
test('la navegación móvil y los formularios no desbordan', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto('/');
await page.getByRole('button', { name: 'Abrir navegación' }).click();
await page
.getByRole('navigation', { name: 'Administración' })
.getByRole('link', { name: 'Lanzamientos', exact: true })
.click();
await expect(page.getByRole('heading', { name: 'Lanzamientos', exact: true })).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(
true
);
await page.screenshot({ path: 'docs/assets/estudio/movil.png', fullPage: true });
});
test('el panel exige sesión para lectura, POST y vista previa', async ({ request }) => {
const r = await request.get('/', { maxRedirects: 0 });
expect(r.status()).toBe(303);
expect(r.headers().location).toMatch(/^\/entrar/);
const post = await request.post('/artistas/nuevo', {
form: { titulo: 'No autorizado' },
maxRedirects: 0,
headers: { origin: 'http://127.0.0.1:5176', accept: 'text/html' }
});
expect(post.status()).toBe(303);
const preview = await request.get('/canciones/no-existe/vista-previa', { maxRedirects: 0 });
expect(preview.status()).toBe(303);
});

Powered by TurnKey Linux.