feat: harden podman deployment orchestration
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from 'node:child_process';
|
||||
import { chromium } from 'playwright';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
|
||||
const frontendUrl = process.env.FRONTEND_URL ?? 'http://127.0.0.1:4173/';
|
||||
const shouldStartPreview = !process.env.FRONTEND_URL;
|
||||
const doFetch = globalThis.fetch;
|
||||
|
||||
async function stopChild(child) {
|
||||
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
||||
try {
|
||||
process.kill(-child.pid, 'SIGTERM');
|
||||
} catch {
|
||||
child.kill('SIGTERM');
|
||||
}
|
||||
await Promise.race([
|
||||
new Promise((resolve) => child.once('exit', resolve)),
|
||||
delay(3000).then(() => {
|
||||
if (child.exitCode === null && child.signalCode === null) {
|
||||
try {
|
||||
process.kill(-child.pid, 'SIGKILL');
|
||||
} catch {
|
||||
child.kill('SIGKILL');
|
||||
}
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
async function waitForPreview() {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
try {
|
||||
const res = await doFetch(frontendUrl);
|
||||
if (res.ok) return;
|
||||
} catch {
|
||||
// Preview is still starting.
|
||||
}
|
||||
await delay(200);
|
||||
}
|
||||
throw new Error(`frontend preview did not become ready at ${frontendUrl}`);
|
||||
}
|
||||
|
||||
let preview = null;
|
||||
if (shouldStartPreview) {
|
||||
preview = spawn(
|
||||
'pnpm',
|
||||
['--filter', '@podman/frontend', 'preview', '--host', '127.0.0.1', '--port', '4173'],
|
||||
{
|
||||
detached: true,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
},
|
||||
);
|
||||
await waitForPreview();
|
||||
}
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
|
||||
|
||||
const consoleErrors = [];
|
||||
const pageErrors = [];
|
||||
const failedRequests = [];
|
||||
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') consoleErrors.push(msg.text());
|
||||
});
|
||||
page.on('pageerror', (err) => pageErrors.push(err.message));
|
||||
page.on('requestfailed', (req) => {
|
||||
failedRequests.push(`${req.url()} ${req.failure()?.errorText ?? ''}`.trim());
|
||||
});
|
||||
|
||||
try {
|
||||
await page.goto(frontendUrl, { waitUntil: 'networkidle', timeout: 30_000 });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const bodyText = await page.locator('body').innerText();
|
||||
const hasPodCards =
|
||||
(await page.locator('text=/Frontend Pod|Backend Pod|graph pod/i').count()) > 0;
|
||||
const hasOverlay = (await page.locator('vite-error-overlay, .vite-error-overlay').count()) > 0;
|
||||
|
||||
if (bodyText.length < 100) throw new Error('frontend rendered too little text');
|
||||
if (!hasPodCards) throw new Error('pod cards did not render');
|
||||
if (hasOverlay) throw new Error('Vite error overlay is visible');
|
||||
|
||||
await page.getByRole('button', { name: 'Join' }).first().click();
|
||||
await page.getByRole('button', { name: 'Share screen' }).waitFor({ timeout: 15_000 });
|
||||
|
||||
const joinedText = await page.locator('body').innerText();
|
||||
const hasPodView =
|
||||
joinedText.includes('Leave pod') &&
|
||||
joinedText.includes('Share screen') &&
|
||||
(joinedText.includes('Test audio') || joinedText.includes('Play beat'));
|
||||
|
||||
if (!hasPodView) {
|
||||
throw new Error(`pod detail controls did not render after join: ${joinedText.slice(0, 500)}`);
|
||||
}
|
||||
if (consoleErrors.length) throw new Error(`console errors: ${consoleErrors.join(' | ')}`);
|
||||
if (pageErrors.length) throw new Error(`page errors: ${pageErrors.join(' | ')}`);
|
||||
if (failedRequests.length) throw new Error(`failed requests: ${failedRequests.join(' | ')}`);
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: true,
|
||||
frontendUrl,
|
||||
bodyLength: bodyText.length,
|
||||
joined: true,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
await browser.close();
|
||||
await stopChild(preview);
|
||||
}
|
||||
Reference in New Issue
Block a user