fix(demo): add HOMEPAGE_ALLOWED_HOSTS, harden Playwright tests

- Set HOMEPAGE_ALLOWED_HOSTS=* so Homepage accepts requests from
  localhost, LAN IPs, and Tailscale FQDNs (appropriate for demo)
- Add host validation to docker-compose.yml.template and demo.env.template
- Bootstrap HOMEPAGE_ALLOWED_HOSTS in ensure_env() for existing installs
- Harden Playwright tests: check for "host validation failed" and
  "internal server error" text, verify page titles, use stronger
  content assertions based on actual rendered content
- Pin @playwright/test to exact 1.52.0 (no caret) to prevent npm
  resolving to a version incompatible with the Docker image
- Gitignore additional Homepage auto-generated files (custom.css/js,
  proxmox.yaml)

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
reachableceo
2026-05-01 13:31:42 -05:00
parent b03f4b2ba2
commit 1628b1dfea
6 changed files with 97 additions and 16 deletions

View File

@@ -1,30 +1,105 @@
import { test, expect } from '@playwright/test';
const services = [
{ name: 'Homepage', url: 'http://localhost:4000', contentCheck: 'homepage' },
{ name: 'Pi-hole', url: 'http://localhost:4006/admin', contentCheck: 'pihole' },
{ name: 'Dockhand', url: 'http://localhost:4007', contentCheck: 'sveltekit' },
{ name: 'InfluxDB', url: 'http://localhost:4008', contentCheck: 'influxdb' },
{ name: 'Grafana', url: 'http://localhost:4009', contentCheck: 'grafana' },
{ name: 'Draw.io', url: 'http://localhost:4010', contentCheck: 'draw' },
{ name: 'Kroki', url: 'http://localhost:4011/health', contentCheck: 'kroki' },
{ name: 'Atomic Tracker', url: 'http://localhost:4012', contentCheck: 'journal' },
{ name: 'ArchiveBox', url: 'http://localhost:4013', contentCheck: 'archive' },
{ name: 'Tube Archivist', url: 'http://localhost:4014', contentCheck: 'tube' },
{ name: 'Wakapi', url: 'http://localhost:4015', contentCheck: 'wakapi' },
{ name: 'MailHog', url: 'http://localhost:4017', contentCheck: 'mailhog' },
{ name: 'Atuin', url: 'http://localhost:4018', contentCheck: 'version' },
{
name: 'Homepage',
url: 'http://localhost:4000',
contentCheck: 'tsys developer support stack',
titleCheck: 'TSYS Developer Support Stack',
},
{
name: 'Pi-hole',
url: 'http://localhost:4006/admin',
contentCheck: 'pihole',
},
{
name: 'Dockhand',
url: 'http://localhost:4007',
contentCheck: 'sveltekit',
},
{
name: 'InfluxDB',
url: 'http://localhost:4008',
contentCheck: 'influxdb',
},
{
name: 'Grafana',
url: 'http://localhost:4009',
contentCheck: 'grafana',
},
{
name: 'Draw.io',
url: 'http://localhost:4010',
contentCheck: 'diagram',
},
{
name: 'Kroki',
url: 'http://localhost:4011/health',
contentCheck: 'kroki',
},
{
name: 'Atomic Tracker',
url: 'http://localhost:4012',
contentCheck: 'journal',
},
{
name: 'ArchiveBox',
url: 'http://localhost:4013',
contentCheck: 'archive',
},
{
name: 'Tube Archivist',
url: 'http://localhost:4014',
contentCheck: 'tubearchivist',
},
{
name: 'Wakapi',
url: 'http://localhost:4015',
contentCheck: 'wakapi',
},
{
name: 'MailHog',
url: 'http://localhost:4017',
contentCheck: 'mailhog',
},
{
name: 'Atuin',
url: 'http://localhost:4018',
contentCheck: 'version',
},
];
for (const svc of services) {
test(`${svc.name} (${svc.url}) loads successfully`, async ({ page }) => {
const response = await page.goto(svc.url, { waitUntil: 'domcontentloaded', timeout: 30000 });
const response = await page.goto(svc.url, {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
expect(response).not.toBeNull();
expect(response!.status()).toBeLessThan(400);
const body = await page.textContent('body').catch(() => '');
const title = await page.title().catch(() => '');
const combined = (body + ' ' + title).toLowerCase();
expect(combined).toContain(svc.contentCheck.toLowerCase());
expect(
combined,
`${svc.name} should not show an error page`
).not.toContain('host validation failed');
expect(
combined,
`${svc.name} should not show a server error`
).not.toContain('internal server error');
expect(
combined,
`${svc.name} should contain expected content`
).toContain(svc.contentCheck.toLowerCase());
if (svc.titleCheck) {
expect(
title.toLowerCase(),
`${svc.name} should have expected title`
).toContain(svc.titleCheck.toLowerCase());
}
});
}