Release 0.7.2
This commit is contained in:
1 parent
64f55a8b1f
commit
d5b1c58ba2
23 files changed
+1630
-455
No files matched your search
+216
-3
@@ -247,6 +247,80 @@ function removeCompletionFixture() {
|
||||
rmSync(path.join(dataDirectory, 'uploads', 'e2e-share.png'), { force: true });
|
||||
}
|
||||
|
||||
function addRssSelectionFixture() {
|
||||
const dataDirectory = process.env.SENDEWERK_E2E_DATA_DIR;
|
||||
if (!dataDirectory) throw new Error('E2E-Datenverzeichnis ist nicht konfiguriert.');
|
||||
const database = new Database(path.join(dataDirectory, 'e2e.sqlite'));
|
||||
const now = '2026-10-26T12:00:00.000Z';
|
||||
const insertFeed = database.prepare(
|
||||
`INSERT INTO feed_sources
|
||||
(id, name, url, is_enabled, refresh_interval_minutes, created_at_utc, updated_at_utc)
|
||||
VALUES (?, ?, ?, 1, 60, ?, ?)`
|
||||
);
|
||||
const insertEntry = database.prepare(
|
||||
`INSERT INTO feed_entries
|
||||
(id, feed_id, external_id, title, url, summary, published_at_utc, first_seen_at_utc, updated_at_utc)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
);
|
||||
database.transaction(() => {
|
||||
insertFeed.run('e2e-feed-a', 'E2E Quelle A', 'https://example.org/e2e-a.xml', now, now);
|
||||
insertFeed.run('e2e-feed-b', 'E2E Quelle B', 'https://example.org/e2e-b.xml', now, now);
|
||||
for (let index = 1; index <= 25; index += 1) {
|
||||
const published = new Date(Date.UTC(2026, 8, index)).toISOString();
|
||||
insertEntry.run(
|
||||
`e2e-entry-a-${index}`,
|
||||
'e2e-feed-a',
|
||||
`a-${index}`,
|
||||
`E2E Quelle A Beitrag ${index}`,
|
||||
`https://example.org/a/${index}`,
|
||||
`Auszug für Beitrag ${index} aus Quelle A.`,
|
||||
published,
|
||||
now,
|
||||
now
|
||||
);
|
||||
}
|
||||
for (let index = 1; index <= 20; index += 1) {
|
||||
const published = new Date(Date.UTC(2026, 7, index)).toISOString();
|
||||
insertEntry.run(
|
||||
`e2e-entry-b-${index}`,
|
||||
'e2e-feed-b',
|
||||
`b-${index}`,
|
||||
`E2E Quelle B Beitrag ${index}`,
|
||||
`https://example.org/b/${index}`,
|
||||
`Auszug für Beitrag ${index} aus Quelle B.`,
|
||||
published,
|
||||
now,
|
||||
now
|
||||
);
|
||||
}
|
||||
})();
|
||||
database.close();
|
||||
}
|
||||
|
||||
function removeIntegrationFixtures() {
|
||||
const dataDirectory = process.env.SENDEWERK_E2E_DATA_DIR;
|
||||
if (!dataDirectory) return;
|
||||
const database = new Database(path.join(dataDirectory, 'e2e.sqlite'));
|
||||
database.pragma('foreign_keys = ON');
|
||||
database.transaction(() => {
|
||||
const pieceIds = database
|
||||
.prepare(
|
||||
"SELECT piece_id AS id FROM feed_entries WHERE id LIKE 'e2e-entry-%' AND piece_id IS NOT NULL"
|
||||
)
|
||||
.all() as { id: string }[];
|
||||
database.prepare("DELETE FROM feed_sources WHERE id LIKE 'e2e-feed-%'").run();
|
||||
for (const piece of pieceIds) database.prepare('DELETE FROM pieces WHERE id = ?').run(piece.id);
|
||||
const accessIds = database
|
||||
.prepare("SELECT id FROM technical_accesses WHERE name LIKE 'E2E API-Zugang%'")
|
||||
.all() as { id: string }[];
|
||||
for (const access of accessIds) {
|
||||
database.prepare('DELETE FROM audit_events WHERE target_id = ?').run(access.id);
|
||||
database.prepare('DELETE FROM technical_accesses WHERE id = ?').run(access.id);
|
||||
}
|
||||
})();
|
||||
database.close();
|
||||
}
|
||||
|
||||
function addPresentationFixture() {
|
||||
const dataDirectory = process.env.SENDEWERK_E2E_DATA_DIR;
|
||||
if (!dataDirectory) throw new Error('E2E-Datenverzeichnis ist nicht konfiguriert.');
|
||||
@@ -1173,6 +1247,124 @@ test('inbox, archive and administration stay concise', async ({ page }) => {
|
||||
await expect(topicIconPicker.locator('input[type="hidden"]')).toHaveValue('satellite-dish');
|
||||
});
|
||||
|
||||
test('rss selection and integration catalogs keep deliberate choices', async ({
|
||||
page
|
||||
}, testInfo) => {
|
||||
addRssSelectionFixture();
|
||||
try {
|
||||
await page.context().route('https://example.org/**', (route) =>
|
||||
route.fulfill({
|
||||
contentType: 'text/html',
|
||||
body: '<!doctype html><title>RSS-Original</title>'
|
||||
})
|
||||
);
|
||||
await page.setViewportSize({ width: 1100, height: 760 });
|
||||
await setTheme(page, 'light');
|
||||
await page.goto('/eingang');
|
||||
const rssPanel = page.locator('.rss-inbox-panel');
|
||||
await expect(rssPanel).toContainText('45 Beiträge sind verfügbar');
|
||||
await rssPanel.getByRole('button', { name: 'RSS-Beiträge auswählen' }).click();
|
||||
const rssDialog = page.getByRole('dialog', { name: 'RSS-Beiträge auswählen' });
|
||||
await expect(rssDialog).toBeVisible();
|
||||
await expect(rssDialog.getByRole('option')).toHaveCount(20);
|
||||
await rssDialog.getByRole('button', { name: 'E2E Quelle B' }).click();
|
||||
await expect(rssDialog).toContainText('25 verfügbare Beiträge');
|
||||
|
||||
const firstChoice = rssDialog.getByRole('option', { name: /E2E Quelle A Beitrag 25/ });
|
||||
await firstChoice.press('Enter');
|
||||
await expect(firstChoice).toHaveAttribute('aria-selected', 'true');
|
||||
const [original] = await Promise.all([
|
||||
page.waitForEvent('popup'),
|
||||
firstChoice.getByRole('link', { name: 'Original öffnen' }).click()
|
||||
]);
|
||||
await original.close();
|
||||
await expect(firstChoice).toHaveAttribute('aria-selected', 'true');
|
||||
await rssDialog.getByRole('button', { name: 'Weiter' }).click();
|
||||
const secondChoice = rssDialog.getByRole('option', { name: /E2E Quelle A Beitrag 1/ });
|
||||
await secondChoice.press(' ');
|
||||
await expect(secondChoice).toHaveAttribute('aria-selected', 'true');
|
||||
await rssDialog.getByRole('button', { name: 'E2E Quelle B' }).click();
|
||||
await expect(
|
||||
rssDialog.getByRole('button', { name: 'Auswahl in den Eingang übernehmen (2)' })
|
||||
).toBeEnabled();
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.evaluate(() => (document.documentElement.dataset.theme = 'dark'));
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await expect(
|
||||
rssDialog.getByRole('button', { name: 'Auswahl in den Eingang übernehmen (2)' })
|
||||
).toBeVisible();
|
||||
await page.screenshot({ path: testInfo.outputPath('rss-selection-mobile-dark.png') });
|
||||
await rssDialog.getByRole('button', { name: 'Auswahl in den Eingang übernehmen (2)' }).click();
|
||||
await expect(page.getByRole('status')).toContainText('2 Beiträge in den Eingang übernommen');
|
||||
await expect(rssPanel).toContainText('43 Beiträge sind verfügbar');
|
||||
await expect(page.getByText('E2E Quelle A Beitrag 25', { exact: true })).toBeVisible();
|
||||
await rssPanel.getByRole('button', { name: 'RSS-Beiträge auswählen' }).click();
|
||||
await expect(rssDialog.getByText('E2E Quelle A Beitrag 25', { exact: true })).toHaveCount(0);
|
||||
await expect(rssDialog.getByText('E2E Quelle A Beitrag 1', { exact: true })).toHaveCount(0);
|
||||
await rssDialog.getByRole('button', { name: 'RSS-Auswahl schließen' }).click();
|
||||
|
||||
await page.setViewportSize({ width: 1100, height: 800 });
|
||||
await setTheme(page, 'light');
|
||||
await page.goto('/verwaltung');
|
||||
await page.getByRole('button', { name: 'Feed hinzufügen' }).click();
|
||||
let feedDialog = page.getByRole('dialog', { name: 'Feed hinzufügen' });
|
||||
await feedDialog.getByLabel('Name').fill('E2E Feed Verwaltung');
|
||||
await feedDialog.getByLabel('Feed-URL').fill('https://example.org/e2e-admin.xml');
|
||||
await feedDialog.getByLabel('Intervall in Minuten').fill('90');
|
||||
await feedDialog.getByLabel('Standardkategorie').selectOption({ index: 1 });
|
||||
await feedDialog.getByRole('button', { name: 'Feed anlegen' }).click();
|
||||
const feedEntry = page.getByRole('button', { name: 'E2E Feed Verwaltung bearbeiten' });
|
||||
await expect(feedEntry).toBeVisible();
|
||||
await feedEntry.click();
|
||||
feedDialog = page.getByRole('dialog', { name: 'E2E Feed Verwaltung' });
|
||||
await feedDialog.getByLabel('Name').fill('E2E Feed Verwaltung bearbeitet');
|
||||
await feedDialog.getByLabel('Intervall in Minuten').fill('120');
|
||||
await feedDialog.getByRole('button', { name: 'Änderungen speichern' }).click();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'E2E Feed Verwaltung bearbeitet bearbeiten' })
|
||||
).toBeVisible();
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.evaluate(() => (document.documentElement.dataset.theme = 'dark'));
|
||||
await page.getByRole('button', { name: 'E2E Feed Verwaltung bearbeitet bearbeiten' }).click();
|
||||
feedDialog = page.getByRole('dialog', { name: 'E2E Feed Verwaltung bearbeitet' });
|
||||
await expect(feedDialog.getByRole('button', { name: 'Änderungen speichern' })).toBeVisible();
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({ path: testInfo.outputPath('feed-editor-mobile-dark.png') });
|
||||
await feedDialog.getByRole('button', { name: 'Feeddialog schließen' }).click();
|
||||
|
||||
await page.setViewportSize({ width: 1100, height: 800 });
|
||||
await page.evaluate(() => (document.documentElement.dataset.theme = 'light'));
|
||||
await page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
await page.getByRole('button', { name: 'Zugang hinzufügen' }).click();
|
||||
let accessDialog = page.getByRole('dialog', { name: 'API-Zugang hinzufügen' });
|
||||
await accessDialog.getByLabel('Name').fill('E2E API-Zugang');
|
||||
await accessDialog.getByLabel('Berechtigung').selectOption('read_write');
|
||||
await accessDialog.getByRole('button', { name: 'Zugang anlegen' }).click();
|
||||
await expect(accessDialog.getByText('Token, einmalig sichtbar')).toBeVisible();
|
||||
const token = await accessDialog.locator('code').textContent();
|
||||
expect(token?.length ?? 0).toBeGreaterThan(20);
|
||||
await accessDialog.getByRole('button', { name: 'Token kopieren' }).click();
|
||||
await expect(accessDialog.getByText('Token kopiert.')).toBeVisible();
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(token);
|
||||
await accessDialog.getByRole('button', { name: 'Zugangsdialog schließen' }).click();
|
||||
await page.getByRole('button', { name: 'E2E API-Zugang bearbeiten' }).click();
|
||||
accessDialog = page.getByRole('dialog', { name: 'E2E API-Zugang' });
|
||||
await accessDialog.getByLabel('Name').fill('E2E API-Zugang bearbeitet');
|
||||
await accessDialog.getByLabel('Berechtigung').selectOption('read');
|
||||
await accessDialog.getByRole('button', { name: 'Änderungen speichern' }).click();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'E2E API-Zugang bearbeitet bearbeiten' })
|
||||
).toContainText('Nur lesen');
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('integration-catalogs-light.png'),
|
||||
fullPage: true
|
||||
});
|
||||
} finally {
|
||||
removeIntegrationFixtures();
|
||||
}
|
||||
});
|
||||
|
||||
test('tools stay editable in pieces after catalog deactivation', async ({ page }, testInfo) => {
|
||||
await page.goto('/verwaltung');
|
||||
for (const width of [390, 1280]) {
|
||||
@@ -1266,8 +1458,9 @@ test('validated YouTube and TikTok players switch without exposing private field
|
||||
);
|
||||
await page.goto('/live?show=e2e-presentation');
|
||||
await page.getByText('Menü', { exact: true }).click();
|
||||
await page.getByText('Ausgabe einrichten', { exact: true }).click();
|
||||
const popupPromise = page.waitForEvent('popup');
|
||||
await page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click();
|
||||
await page.getByRole('button', { name: 'Mit dieser Sendung verbinden und öffnen' }).click();
|
||||
const output = await popupPromise;
|
||||
await expect(output.locator('.output-content')).toHaveCount(0);
|
||||
await expect(output.locator('.heinzlive-output')).toHaveClass(/empty/);
|
||||
@@ -1360,11 +1553,23 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
const liveMenu = page.getByRole('dialog', { name: 'Regiemenü' });
|
||||
await expect(liveMenu).toBeVisible();
|
||||
await expect(liveMenu).toContainText('Herbstsendung: Kleine Welten');
|
||||
await expect(liveMenu).toContainText('Läuft');
|
||||
await expect(liveMenu.getByRole('button', { name: 'Live-Stand wiederaufnehmen' })).toHaveCount(0);
|
||||
await expect(liveMenu.getByRole('button', { name: 'Pausieren', exact: true })).toHaveCount(0);
|
||||
await expect(liveMenu.getByRole('button', { name: 'Fortsetzen', exact: true })).toHaveCount(0);
|
||||
await expect(liveMenu.getByText(/zurücksetzen/)).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: 'Schrift verkleinern' })).toBeFocused();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(liveMenu).toBeHidden();
|
||||
await expect(liveMenuTrigger).toBeFocused();
|
||||
await page.getByRole('button', { name: 'Sendung pausieren' }).click();
|
||||
await expect(page.getByRole('button', { name: 'Sendung fortsetzen' })).toBeVisible();
|
||||
await liveMenuTrigger.click();
|
||||
await expect(liveMenu).toContainText('Pausiert');
|
||||
await expect(liveMenu.getByRole('button', { name: 'Fortsetzen', exact: true })).toHaveCount(0);
|
||||
await page.keyboard.press('Escape');
|
||||
await page.getByRole('button', { name: 'Sendung fortsetzen' }).click();
|
||||
await expect(page.getByRole('button', { name: 'Sendung pausieren' })).toBeVisible();
|
||||
await liveMenuTrigger.press('Enter');
|
||||
await expect(liveMenu).toBeVisible();
|
||||
await page.locator('.live-current').click({ position: { x: 8, y: 8 } });
|
||||
@@ -1392,9 +1597,10 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
await expect(page.locator('.research-details')).toHaveAttribute('open', '');
|
||||
|
||||
await page.getByText('Menü', { exact: true }).click();
|
||||
await page.getByText('Ausgabe einrichten', { exact: true }).click();
|
||||
const [popup] = await Promise.all([
|
||||
page.waitForEvent('popup'),
|
||||
page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click()
|
||||
page.getByRole('button', { name: 'Mit dieser Sendung verbinden und öffnen' }).click()
|
||||
]);
|
||||
await popup.waitForURL(/\/heinzlive\/default#/);
|
||||
const outputUrl = popup.url();
|
||||
@@ -1712,12 +1918,19 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
window.open = () => null;
|
||||
});
|
||||
await page.getByText('Menü', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click();
|
||||
await page.getByText('Ausgabe einrichten', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Ausgabe öffnen' }).click();
|
||||
await expect(
|
||||
page
|
||||
.getByRole('status')
|
||||
.filter({ hasText: 'Präsentationsfenster konnte nicht geöffnet werden' })
|
||||
).toBeVisible();
|
||||
await page.getByText('Menü', { exact: true }).click();
|
||||
page.once('dialog', (dialog) => dialog.accept());
|
||||
await page.getByRole('button', { name: 'Sendung beenden' }).click();
|
||||
await page.getByText('Menü', { exact: true }).click();
|
||||
await expect(page.getByRole('dialog', { name: 'Regiemenü' })).toContainText('Lauf beendet');
|
||||
await expect(page.getByRole('button', { name: 'Beendeten Lauf wiederaufnehmen' })).toBeVisible();
|
||||
await anonymous.close();
|
||||
await popup.close();
|
||||
await expectNoHorizontalOverflow(page);
|
||||
|
||||
Reference in new issue
Block a user