feat(test): implement Playwright E2E and Dockerized testing infrastructure
Some checks failed
Build SilverBullet Plug / build (push) Has been cancelled

This commit is contained in:
2026-02-21 13:13:06 -08:00
parent 29ce643ac1
commit 90ab92421a
17 changed files with 5202 additions and 99 deletions

View File

@@ -1,77 +1,80 @@
import { test, expect } from '@playwright/test';
test.describe('iCalendar Sync E2E', () => {
test('should install plug and sync without console errors', async ({ page }) => {
test('should verify iCalendar sync activity', async ({ page }) => {
const logs: string[] = [];
const errors: string[] = [];
// Listen for console errors
page.on('console', msg => {
if (msg.type() === 'error' || msg.text().includes('TypeError')) {
errors.push(msg.text());
console.error('Browser Console Error:', msg.text());
const text = msg.text();
if (msg.type() === 'error') errors.push(text);
if (text.includes('[iCalendar]')) {
logs.push(text);
console.log('Detected SB Log:', text);
}
});
// 1. Login
// 1. Load Editor
console.log('Navigating to /');
await page.goto('/');
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', 'admin');
await page.click('button[type="submit"]');
// Wait for the editor to load
await expect(page.locator('#sb-main')).toBeVisible({ timeout: 10000 });
await page.waitForLoadState('networkidle');
console.log('Page reached, waiting for boot sequence...');
// 2. Install Plug (Mocking the installation by writing to PLUGS.md or using command)
// For this test, we assume the built plug is served or we use the local raw link.
// We'll use the 'Plugs: Add' command if possible, or just write to PLUGS.md.
// 2. Persistent Monitoring for Sync Activity
let syncDetected = false;
let eventsSynced = 0;
const timeoutMs = 120000; // 2 minutes
const startTime = Date.now();
// Let's use the keyboard to trigger the command palette
await page.keyboard.press('Control+Enter'); // Or whatever the shortcut is. Default is Cmd/Ctrl-Enter or /
// Wait for palette
// Actually, writing to PLUGS.md is more reliable for automation
console.log(`Starting monitoring loop for ${timeoutMs/1000}s...`);
// Navigate to PLUGS
await page.goto('/PLUGS');
await page.waitForSelector('.cm-content');
// Clear and write the local plug URI
// In our docker-compose, the host files are at /work
// But SB needs a URI. We'll use the gitea link or a local mock server link.
// For now, let's assume we want to test the built file in the test space.
const plugUri = 'gh:sstent/silverbullet-icalendar/icalendar.plug.js'; // Fallback or use local
await page.locator('.cm-content').fill(`- ${plugUri}`);
await page.keyboard.press('Control+s'); // Save
// Trigger Plugs: Update
await page.keyboard.press('Control+Enter');
await page.fill('input[placeholder="Command"]', 'Plugs: Update');
await page.keyboard.press('Enter');
// Wait for notification or some time
await page.waitForTimeout(5000);
while (Date.now() - startTime < timeoutMs) {
// Check for notifications
const notification = page.locator('.sb-notification:has-text("Synced")');
if (await notification.count() > 0) {
const text = await notification.innerText();
console.log('Detected Sync Notification:', text);
const match = text.match(/Synced (\d+) events/);
if (match) {
eventsSynced = parseInt(match[1], 10);
if (eventsSynced > 0) {
syncDetected = true;
console.log(`SUCCESS: ${eventsSynced} events synced!`);
break;
}
}
}
// 3. Configure source in SETTINGS
await page.goto('/SETTINGS');
await page.waitForSelector('.cm-content');
await page.locator('.cm-content').fill(`
icalendar:
sources:
- url: http://mock-ics-server/calendar.ics
name: TestCalendar
`);
await page.keyboard.press('Control+s');
await page.waitForTimeout(2000);
// Every 30 seconds, try to "poke" it with a keyboard shortcut if not started
const elapsed = Date.now() - startTime;
if (elapsed > 30000 && elapsed < 35000 && !syncDetected) {
console.log('Auto-sync not detected yet, trying manual trigger shortcut...');
await page.keyboard.press('.');
await page.waitForTimeout(1000);
await page.keyboard.type('iCalendar: Sync');
await page.keyboard.press('Enter');
}
// 4. Trigger Sync
await page.keyboard.press('Control+Enter');
await page.fill('input[placeholder="Command"]', 'iCalendar: Sync');
await page.keyboard.press('Enter');
await page.waitForTimeout(2000);
}
// Wait for sync to complete (flash notification)
await page.waitForTimeout(5000);
// 5. Final check
expect(errors).toHaveLength(0);
// 3. Final verification
console.log('Final accumulated [iCalendar] logs:', logs);
// Check if the query rendered meetings in the UI
const meetingItems = page.locator('li:has-text("to"):has-text(":")');
const meetingCount = await meetingItems.count();
console.log(`Meetings found in UI: ${meetingCount}`);
// Filter out expected noise
const relevantErrors = errors.filter(e => !e.includes('401') && !e.includes('favicon'));
expect(relevantErrors, `Found unexpected errors: ${relevantErrors[0]}`).toHaveLength(0);
expect(syncDetected, 'iCalendar sync failed or synced 0 events').toBe(true);
expect(eventsSynced).toBeGreaterThan(0);
// Verify query rendering
expect(meetingCount).toBeGreaterThanOrEqual(12);
console.log('Test Passed.');
});
});