mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-28 01:33:35 +00:00
fix(e2e): require dashboard smoke assets
This commit is contained in:
@@ -1013,9 +1013,31 @@ sleep 1`,
|
||||
deadline=$((SECONDS + 120))
|
||||
while [ $SECONDS -lt $deadline ]; do
|
||||
if curl -fsSL --connect-timeout 2 --max-time 5 http://127.0.0.1:18789/ >/tmp/openclaw-dashboard-smoke.html 2>/dev/null; then
|
||||
grep -F '<title>OpenClaw Control</title>' /tmp/openclaw-dashboard-smoke.html >/dev/null &&
|
||||
grep -F '<openclaw-app></openclaw-app>' /tmp/openclaw-dashboard-smoke.html >/dev/null &&
|
||||
exit 0
|
||||
if grep -F '<title>OpenClaw Control</title>' /tmp/openclaw-dashboard-smoke.html >/dev/null &&
|
||||
grep -F '<openclaw-app></openclaw-app>' /tmp/openclaw-dashboard-smoke.html >/dev/null; then
|
||||
asset_paths="$(
|
||||
sed -nE 's/.*<(script|link)[^>]*(src|href)=["'"'"']([^"'"'"']+)["'"'"'].*/\3/p' /tmp/openclaw-dashboard-smoke.html |
|
||||
grep -E '(^|/)assets/' |
|
||||
grep -Ev '^(https?:)?//' |
|
||||
sort -u
|
||||
)"
|
||||
if [ -n "$asset_paths" ]; then
|
||||
assets_ok=1
|
||||
while IFS= read -r asset_path; do
|
||||
[ -n "$asset_path" ] || continue
|
||||
case "$asset_path" in
|
||||
http://127.0.0.1:18789/*) asset_url="$asset_path" ;;
|
||||
/*) asset_url="http://127.0.0.1:18789$asset_path" ;;
|
||||
*) asset_url="http://127.0.0.1:18789/$asset_path" ;;
|
||||
esac
|
||||
curl -fsSL --connect-timeout 2 --max-time 5 "$asset_url" >/dev/null 2>/dev/null ||
|
||||
assets_ok=0
|
||||
done <<EOF
|
||||
$asset_paths
|
||||
EOF
|
||||
[ "$assets_ok" -eq 1 ] && exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
@@ -2566,6 +2566,57 @@ export async function readBoundedCrossOsResponseText(
|
||||
return truncated ? `${text}\n[truncated]` : text;
|
||||
}
|
||||
|
||||
export function dashboardHtmlMarkerStatus(html: string): {
|
||||
app: boolean;
|
||||
ready: boolean;
|
||||
title: boolean;
|
||||
} {
|
||||
const title = html.includes("<title>OpenClaw Control</title>");
|
||||
const app = html.includes("<openclaw-app></openclaw-app>");
|
||||
return { app, ready: title && app, title };
|
||||
}
|
||||
|
||||
export function resolveDashboardAssetUrls(dashboardUrl: string, html: string): string[] {
|
||||
const baseUrl = new URL(dashboardUrl);
|
||||
const assetUrls = new Set<string>();
|
||||
const assetAttributePattern = /<(?:script|link)\b[^>]*(?:src|href)\s*=\s*(["'])([^"']+)\1/giu;
|
||||
for (const match of html.matchAll(assetAttributePattern)) {
|
||||
const rawUrl = match[2]?.trim();
|
||||
if (!rawUrl) {
|
||||
continue;
|
||||
}
|
||||
const assetUrl = new URL(rawUrl, baseUrl);
|
||||
if (assetUrl.origin === baseUrl.origin && assetUrl.pathname.includes("/assets/")) {
|
||||
assetUrls.add(assetUrl.href);
|
||||
}
|
||||
}
|
||||
return [...assetUrls].toSorted();
|
||||
}
|
||||
|
||||
export async function verifyDashboardAssetUrls(
|
||||
assetUrls: string[],
|
||||
fetchAsset: typeof fetch = fetch,
|
||||
): Promise<{ failures: string[]; ok: boolean }> {
|
||||
if (assetUrls.length === 0) {
|
||||
return { failures: ["no dashboard asset URLs found"], ok: false };
|
||||
}
|
||||
const failures: string[] = [];
|
||||
for (const assetUrl of assetUrls) {
|
||||
try {
|
||||
const response = await fetchAsset(assetUrl, {
|
||||
signal: AbortSignal.timeout(CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS),
|
||||
});
|
||||
await response.body?.cancel().catch(() => undefined);
|
||||
if (!response.ok) {
|
||||
failures.push(`${assetUrl} status=${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
failures.push(`${assetUrl} ${formatError(error)}`);
|
||||
}
|
||||
}
|
||||
return { failures, ok: failures.length === 0 };
|
||||
}
|
||||
|
||||
async function waitForDiscordMessage(params) {
|
||||
const deadline = Date.now() + 3 * 60 * 1000;
|
||||
while (Date.now() < deadline) {
|
||||
@@ -3437,18 +3488,22 @@ async function runDashboardSmoke(params) {
|
||||
signal: AbortSignal.timeout(CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS),
|
||||
});
|
||||
const html = await readBoundedCrossOsResponseText(response);
|
||||
if (
|
||||
response.ok &&
|
||||
html.includes("<title>OpenClaw Control</title>") &&
|
||||
html.includes("<openclaw-app></openclaw-app>")
|
||||
) {
|
||||
const markers = dashboardHtmlMarkerStatus(html);
|
||||
const assetUrls = resolveDashboardAssetUrls(dashboardUrl, html);
|
||||
if (response.ok && markers.ready) {
|
||||
const assets = await verifyDashboardAssetUrls(assetUrls);
|
||||
if (assets.ok) {
|
||||
logStream.write(
|
||||
`${new Date().toISOString()} dashboard-ready status=${response.status} assets=${assetUrls.length}\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
logStream.write(
|
||||
`${new Date().toISOString()} dashboard-ready status=${response.status}\n`,
|
||||
`${new Date().toISOString()} dashboard-assets-not-ready status=${response.status} assets=${assetUrls.length} failures=${assets.failures.join(" | ")}\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
logStream.write(
|
||||
`${new Date().toISOString()} dashboard-not-ready status=${response.status} title=${html.includes("<title>OpenClaw Control</title>")} app=${html.includes("<openclaw-app></openclaw-app>")}\n`,
|
||||
`${new Date().toISOString()} dashboard-not-ready status=${response.status} title=${markers.title} app=${markers.app} assets=${assetUrls.length}\n`,
|
||||
);
|
||||
} catch (error) {
|
||||
logStream.write(
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
canConnectToLoopbackPort,
|
||||
buildDiscordSmokeGuildsConfig,
|
||||
buildRealUpdateEnv,
|
||||
dashboardHtmlMarkerStatus,
|
||||
CROSS_OS_FETCH_BODY_MAX_CHARS,
|
||||
CROSS_OS_GATEWAY_READY_TIMEOUT_MS,
|
||||
CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS,
|
||||
@@ -61,6 +62,7 @@ import {
|
||||
readInstalledVersion,
|
||||
readBoundedCrossOsResponseText,
|
||||
readRunnerOverrideEnv,
|
||||
resolveDashboardAssetUrls,
|
||||
resolveCrossOsAgentTurnOptional,
|
||||
runCommand,
|
||||
resolveCommandSpawnInvocation,
|
||||
@@ -85,6 +87,7 @@ import {
|
||||
shouldSkipOptionalCrossOsAgentTurnError,
|
||||
shouldUseManagedGatewayForInstallerRuntime,
|
||||
shouldUseManagedGatewayService,
|
||||
verifyDashboardAssetUrls,
|
||||
verifyDevUpdateStatus,
|
||||
verifyPackagedUpgradeUpdateResult,
|
||||
verifyWindowsPackagedUpgradeFallbackInstall,
|
||||
@@ -159,6 +162,40 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
||||
expect(CROSS_OS_FETCH_BODY_MAX_CHARS).toBeGreaterThan(1024);
|
||||
});
|
||||
|
||||
it("requires dashboard root markers and same-origin asset URLs", () => {
|
||||
const html = [
|
||||
"<title>OpenClaw Control</title>",
|
||||
"<openclaw-app></openclaw-app>",
|
||||
'<link rel="stylesheet" href="/assets/index.css">',
|
||||
'<script type="module" src="assets/index.js"></script>',
|
||||
'<script type="module" src="https://example.com/assets/ignored.js"></script>',
|
||||
].join("\n");
|
||||
|
||||
expect(dashboardHtmlMarkerStatus(html)).toEqual({ app: true, ready: true, title: true });
|
||||
expect(resolveDashboardAssetUrls("http://127.0.0.1:18789/", html)).toEqual([
|
||||
"http://127.0.0.1:18789/assets/index.css",
|
||||
"http://127.0.0.1:18789/assets/index.js",
|
||||
]);
|
||||
});
|
||||
|
||||
it("fails dashboard readiness when assets are missing or unreachable", async () => {
|
||||
await expect(verifyDashboardAssetUrls([])).resolves.toEqual({
|
||||
failures: ["no dashboard asset URLs found"],
|
||||
ok: false,
|
||||
});
|
||||
|
||||
const result = await verifyDashboardAssetUrls(
|
||||
["http://127.0.0.1:18789/assets/index.css", "http://127.0.0.1:18789/assets/index.js"],
|
||||
async (url) =>
|
||||
new Response("", {
|
||||
status: String(url).endsWith(".js") ? 404 : 200,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.failures).toEqual(["http://127.0.0.1:18789/assets/index.js status=404"]);
|
||||
});
|
||||
|
||||
it("keeps gateway RPC status probes patient enough for live release startup", () => {
|
||||
expect(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS).toBeGreaterThanOrEqual(30_000);
|
||||
expect(CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS).toBeGreaterThan(
|
||||
|
||||
@@ -806,6 +806,14 @@ if (isPrlctl) {
|
||||
expect(windows).toContain("OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS");
|
||||
});
|
||||
|
||||
it("requires macOS dashboard smoke to load built assets", () => {
|
||||
const macos = readFileSync(TS_PATHS.macos, "utf8");
|
||||
|
||||
expect(macos).toContain("asset_paths=");
|
||||
expect(macos).toContain("grep -E '(^|/)assets/'");
|
||||
expect(macos).toContain('curl -fsSL --connect-timeout 2 --max-time 5 "$asset_url"');
|
||||
});
|
||||
|
||||
it("passes aggregate model overrides into each OS fresh lane", () => {
|
||||
const script = readFileSync(TS_PATHS.npmUpdate, "utf8");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user