Log tabs with a daemon I/O view, and Playwright UI tests
The log view splits into All / Daemon I/O / Scans / HTTP. Daemon I/O is the control protocol itself, logged where every command funnels through so it covers socket clients, the CLI and the web UI alike. stderr and the in-app buffer now have separate filters, so the UI can keep debug detail the terminal should not carry. Playwright drives a real browser against a daemon on fixture feeds. Eight tests, each mapping to a bug that reached a user -- the Rust tests and the stub-DOM smoke test cannot see a wrong selector or a dead handler. It immediately found one: OPML folders rendered expanded by default, because the code stored closed groups, so any folder never toggled counted as open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
BIN
tests/ui/fixtures/ep1.mp3
Normal file
BIN
tests/ui/fixtures/ep1.mp3
Normal file
Binary file not shown.
5
tests/ui/fixtures/other.xml
Normal file
5
tests/ui/fixtures/other.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<rss version="2.0"><channel><title>Grouped Show</title><link>http://127.0.0.1:8792/</link>
|
||||
<description>Inside the OPML.</description>
|
||||
<item><title>Grouped Ep</title><guid>g-1</guid><description>x</description></item>
|
||||
</channel></rss>
|
||||
19
tests/ui/fixtures/serve.js
Normal file
19
tests/ui/fixtures/serve.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// Serves the fixture feeds so the daemon under test has something real to scan.
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const dir = __dirname;
|
||||
const port = Number(process.env.FIXTURE_PORT || 8792);
|
||||
|
||||
http.createServer((req, res) => {
|
||||
const name = decodeURIComponent(req.url.split('?')[0].replace(/^\//, '')) || 'index';
|
||||
const file = path.join(dir, path.basename(name));
|
||||
fs.readFile(file, (err, body) => {
|
||||
if (err) { res.writeHead(404).end('no'); return; }
|
||||
const type = file.endsWith('.mp3') ? 'audio/mpeg'
|
||||
: file.endsWith('.opml') ? 'text/x-opml' : 'application/xml';
|
||||
res.writeHead(200, { 'content-type': type, 'content-length': body.length });
|
||||
res.end(body);
|
||||
});
|
||||
}).listen(port, '127.0.0.1', () => console.log(`fixtures on ${port}`));
|
||||
15
tests/ui/fixtures/show.xml
Normal file
15
tests/ui/fixtures/show.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0"?>
|
||||
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||
<channel><title>Test Show</title><link>http://127.0.0.1:8792/</link><description>A fixture feed.</description>
|
||||
<itunes:image href="http://127.0.0.1:8792/art.png"/>
|
||||
<item><title>First Episode</title><guid>ui-1</guid>
|
||||
<pubDate>Mon, 01 Sep 2026 10:00:00 +0000</pubDate>
|
||||
<description><p>Show notes for the first one.</p></description>
|
||||
<itunes:duration>1830</itunes:duration><itunes:season>1</itunes:season><itunes:episode>1</itunes:episode>
|
||||
<enclosure url="http://127.0.0.1:8792/ep1.mp3" length="40000" type="audio/mpeg"/></item>
|
||||
<item><title>Second Episode</title><guid>ui-2</guid>
|
||||
<pubDate>Mon, 08 Sep 2026 10:00:00 +0000</pubDate>
|
||||
<description>Notes for the second.</description>
|
||||
<itunes:duration>900</itunes:duration>
|
||||
<enclosure url="http://127.0.0.1:8792/ep1.mp3?2" length="40000" type="audio/mpeg"/></item>
|
||||
</channel></rss>
|
||||
4
tests/ui/fixtures/subs.opml
Normal file
4
tests/ui/fixtures/subs.opml
Normal file
@@ -0,0 +1,4 @@
|
||||
<opml version="2.0"><head><title>Test Subscriptions</title></head>
|
||||
<body><outline text="Folder">
|
||||
<outline type="rss" text="Grouped Show" xmlUrl="http://127.0.0.1:8792/other.xml"/>
|
||||
</outline></body></opml>
|
||||
Reference in New Issue
Block a user