From 61c38bacc188c378f8a1b6745628c31b39728296 Mon Sep 17 00:00:00 2001 From: rays Date: Sat, 14 Nov 2026 03:58:55 +0000 Subject: [PATCH] Browser suite: every service's /metrics, scraped as Prometheus does Nothing in the browser suite asked ipx, the directory or ipx-play for their metrics, though production's Prometheus scrapes all three every 15 s and the dashboard and alerts go blank if one stops answering. The suite's servers get metrics ports (8799, 8797, 8790) and one test reads each gzipped, checks every sample line is one Prometheus can parse, and looks for the series that show it works: ipx's request histogram with the route just asked for, the directory's snapshot from ipx, ipx-play's build. ipx's metrics.rs went from 9% to 86% of lines run by the suite. Co-Authored-By: Claude Opus 5.5 --- tests/ui/app.spec.ts | 32 ++++++++++++++++++++++++++++++++ tests/ui/global-setup.ts | 4 ++++ 2 files changed, 36 insertions(+) diff --git a/tests/ui/app.spec.ts b/tests/ui/app.spec.ts index 18b6a51..2c55ce8 100644 --- a/tests/ui/app.spec.ts +++ b/tests/ui/app.spec.ts @@ -2883,3 +2883,35 @@ test('an error toast is announced and stays until it is dismissed', async ({ pag await t.getByRole('button', { name: 'Dismiss' }).click(); await expect(t).toHaveCount(0); }); + +/// A scrape as Prometheus makes it: gzipped (#253), and every sample a name, labels perhaps, and a +/// number, since one line that is not fails the whole scrape. +async function scrape(page: Page, port: number): Promise { + const r = await page.request.get(`http://127.0.0.1:${port}/metrics`, { headers: { 'Accept-Encoding': 'gzip' } }); + expect(r.status()).toBe(200); + expect(r.headers()['content-type']).toContain('text/plain'); + const text = await r.text(); + const bad = text.split('\n').filter(l => l && !l.startsWith('#') && !/^[a-z_:][a-z0-9_:]*(\{.*\})? (-?[0-9.e+-]+|NaN|\+Inf)$/i.test(l)); + expect(bad, `port ${port}`).toEqual([]); + return text; +} + +test('each service serves its metrics as Prometheus reads them, the requests made counted by route', async ({ page }) => { + // #194, #370, #458: production's Prometheus scrapes these every 15 s; the dashboard and the + // alerts go blank if one stops answering, and nothing else in the suite asks for them. + await page.request.get('/api/feeds'); + const ipx = await scrape(page, 8799); + expect(ipx).toMatch(/^# TYPE ipx_http_request_duration_seconds histogram$/m); + expect(ipx).toMatch(/^ipx_http_request_duration_seconds_count\{method="GET",route="\/api\/feeds",status="200"\} [1-9]/m); + expect(ipx).toMatch(/^ipx_build_info\{version="[^"]+"\} 1$/m); + expect(ipx).toMatch(/^ipx_db_connections_max [1-9]/m); + expect(ipx).toMatch(/^ipx_feeds [1-9]/m); + // The suite's ipx shares with its directory, so the directory has taken a snapshot and answered. + const dir = await scrape(page, 8797); + expect(dir).toMatch(/^directory_build_info\{version="[^"]+"\} 1$/m); + expect(dir).toMatch(/^directory_snapshots_total [1-9]/m); + expect(dir).toMatch(/^directory_http_request_duration_seconds_count\{/m); + const play = await scrape(page, 8790); + expect(play).toMatch(/^play_build_info\{version="[^"]+"\} 1$/m); + expect(play).toMatch(/^play_uptime_seconds [0-9]/m); +}); diff --git a/tests/ui/global-setup.ts b/tests/ui/global-setup.ts index a9d6d02..b0237ef 100644 --- a/tests/ui/global-setup.ts +++ b/tests/ui/global-setup.ts @@ -56,6 +56,8 @@ token = "${TOKEN}" trusted_header = "X-Test-User" trusted_proxies = ["127.0.0.1"] sign_out_url = "/signed-out-by-the-proxy" +# What Prometheus scrapes in production (#194), read by a test once the others have made requests. +metrics_bind = "127.0.0.1:8799" [feeds.test-show] url = "http://127.0.0.1:8792/show.xml" @@ -82,6 +84,7 @@ auto_download = false `); fs.writeFileSync(path.join(root, 'directory.toml'), ` bind = "127.0.0.1:8795" +metrics_bind = "127.0.0.1:8797" database_url_env = "DIRECTORY_DATABASE_URL" # The fixture feeds are on loopback, which the refresher refuses otherwise (#329). fetch_internal = ["127.0.0.1"] @@ -93,6 +96,7 @@ token_sha256 = "${crypto.createHash('sha256').update(DIRECTORY_TOKEN).digest('he `); fs.writeFileSync(path.join(root, 'play.toml'), ` bind = "127.0.0.1:8798" +metrics_bind = "127.0.0.1:8790" public_url = "${PLAY}" [[tokens]]