mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
fix: stabilize CI smoke and local-auth gates (#2850)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { stubExternalMediaInVitePreview } from "./helpers/externalMedia";
|
||||
import { expectHealthyPage, trackRuntimeErrors } from "./helpers/runtimeErrors";
|
||||
|
||||
test("public navigation routes render without runtime errors", async ({ browser }) => {
|
||||
@@ -9,6 +10,7 @@ test("public navigation routes render without runtime errors", async ({ browser
|
||||
|
||||
for (const route of routes) {
|
||||
const page = await browser.newPage();
|
||||
await stubExternalMediaInVitePreview(page);
|
||||
const errors = trackRuntimeErrors(page);
|
||||
|
||||
await page.goto(route.path, { waitUntil: "domcontentloaded" });
|
||||
@@ -19,6 +21,7 @@ test("public navigation routes render without runtime errors", async ({ browser
|
||||
});
|
||||
|
||||
test("signed-out publish entry renders", async ({ page }) => {
|
||||
await stubExternalMediaInVitePreview(page);
|
||||
const errors = trackRuntimeErrors(page);
|
||||
|
||||
await page.goto("/upload", { waitUntil: "domcontentloaded" });
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
const OPENCLAW_MEDIA_EXTENSIONS = new Set([
|
||||
".avif",
|
||||
".gif",
|
||||
".ico",
|
||||
".jpeg",
|
||||
".jpg",
|
||||
".png",
|
||||
".svg",
|
||||
".webp",
|
||||
]);
|
||||
|
||||
export function isKnownOpenClawMediaUrl(url: string) {
|
||||
let parsedUrl: URL;
|
||||
try {
|
||||
parsedUrl = new URL(url);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedUrl.origin !== "https://openclaw.ai") return false;
|
||||
if (parsedUrl.pathname === "/favicon.svg") return true;
|
||||
if (!parsedUrl.pathname.startsWith("/ecosystem/")) return false;
|
||||
|
||||
const extension = parsedUrl.pathname.match(/\.[^./]+$/)?.[0]?.toLowerCase();
|
||||
return extension !== undefined && OPENCLAW_MEDIA_EXTENSIONS.has(extension);
|
||||
}
|
||||
|
||||
export async function stubExternalMediaInVitePreview(page: Page) {
|
||||
if (process.env.PLAYWRIGHT_BASE_URL) return;
|
||||
|
||||
await page.route("**/_vercel/image?**", (route) => route.fulfill({ status: 204 }));
|
||||
|
||||
await page.route("https://openclaw.ai/**", (route) => {
|
||||
if (isKnownOpenClawMediaUrl(route.request().url())) {
|
||||
return route.fulfill({ status: 204 });
|
||||
}
|
||||
return route.continue();
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,12 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
import { expect, type ConsoleMessage, type Page } from "@playwright/test";
|
||||
import { isKnownOpenClawMediaUrl } from "./externalMedia";
|
||||
|
||||
const EXTERNAL_RESOURCE_DNS_ERROR = "Failed to load resource: net::ERR_NAME_NOT_RESOLVED";
|
||||
|
||||
function isIgnoredExternalResourceDnsError(message: ConsoleMessage) {
|
||||
if (message.text() !== EXTERNAL_RESOURCE_DNS_ERROR) return false;
|
||||
return isKnownOpenClawMediaUrl(message.location().url);
|
||||
}
|
||||
|
||||
export function trackRuntimeErrors(page: Page) {
|
||||
const errors: string[] = [];
|
||||
@@ -9,6 +17,7 @@ export function trackRuntimeErrors(page: Page) {
|
||||
|
||||
page.on("console", (message) => {
|
||||
if (message.type() !== "error") return;
|
||||
if (isIgnoredExternalResourceDnsError(message)) return;
|
||||
errors.push(`console:${message.text()}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect, test, type APIRequestContext, type Page } from "@playwright/test";
|
||||
import { stubExternalMediaInVitePreview } from "./helpers/externalMedia";
|
||||
import { expectHealthyPage, trackRuntimeErrors, waitForHydration } from "./helpers/runtimeErrors";
|
||||
|
||||
type SeedFixtures = {
|
||||
@@ -32,12 +33,6 @@ function seedApiUrl(path: string) {
|
||||
return convexSiteUrl ? new URL(path, convexSiteUrl).toString() : path;
|
||||
}
|
||||
|
||||
async function stubVercelImageOptimizerInVitePreview(page: Page) {
|
||||
if (process.env.PLAYWRIGHT_BASE_URL) return;
|
||||
// Vite preview does not serve Vercel's production-only image optimizer.
|
||||
await page.route("**/_vercel/image?**", (route) => route.fulfill({ status: 204 }));
|
||||
}
|
||||
|
||||
async function getSeedFixture(request: APIRequestContext, path: string) {
|
||||
let lastResponse: Awaited<ReturnType<APIRequestContext["get"]>> | null = null;
|
||||
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
||||
@@ -212,7 +207,7 @@ async function expectPublicRouteHealthy(
|
||||
route: PublicRouteCase,
|
||||
fixtures: SeedFixtures,
|
||||
) {
|
||||
await stubVercelImageOptimizerInVitePreview(page);
|
||||
await stubExternalMediaInVitePreview(page);
|
||||
const errors = trackRuntimeErrors(page);
|
||||
const path = route.path(fixtures);
|
||||
const response = await page.goto(path, { waitUntil: "domcontentloaded" });
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
"ci:pr": "bun run ci:static && bun run ci:unit && bun run ci:packages && bun run ci:types-build && bun run ci:e2e-http",
|
||||
"ci:static": "bun run check:peers && bun audit --ignore GHSA-rmmr-r34h-pfm5 --ignore GHSA-gv7w-rqvm-qjhr --ignore GHSA-g7r4-m6w7-qqqr --ignore GHSA-x4vx-rjvf-j5p4 --ignore GHSA-76mc-f452-cxcm --ignore GHSA-hpcv-96wg-7vj8 --ignore GHSA-r47g-fvhr-h676 --ignore GHSA-vxr8-fq34-vvx9 --ignore GHSA-gvmj-g25r-r7wr --ignore GHSA-rp9w-3fw7-7cwq --ignore GHSA-cmwh-pvxp-8882 --ignore GHSA-vmh5-mc38-953g --ignore GHSA-pr7r-676h-xcf6 && bun run llms:check && bun run format:check && bun run lint && bun run deadcode:ci",
|
||||
"ci:types-build": "bunx tsc --noEmit && bunx tsc -p packages/schema/tsconfig.json --noEmit && bunx tsc -p packages/clawhub/tsconfig.json --noEmit && bun run --cwd packages/clawhub-admin typecheck && VITE_CONVEX_URL=https://example.invalid bun run build",
|
||||
"ci:unit": "VITE_CONVEX_URL=https://example.invalid bun run coverage",
|
||||
"ci:unit": "SITE_URL= VITE_SITE_URL= VITE_CONVEX_URL=https://example.invalid bun run coverage",
|
||||
"clawscan:local": "bun scripts/local-clawscan-dry-run.ts",
|
||||
"convex:deploy": "bunx convex deploy --typecheck=disable --yes",
|
||||
"coverage": "vitest run --coverage",
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveLocalAuthRunnerConfig } from "./playwright-local-auth-config";
|
||||
import {
|
||||
resolveLocalAuthDeployment,
|
||||
resolveLocalAuthRunnerConfig,
|
||||
} from "./playwright-local-auth-config";
|
||||
|
||||
describe("playwright local-auth runner config", () => {
|
||||
it("defaults local-auth Convex to the anonymous deployment marker", () => {
|
||||
expect(resolveLocalAuthDeployment(undefined, null)).toBe("anonymous:anonymous-agent");
|
||||
expect(resolveLocalAuthDeployment(undefined, undefined)).toBe("anonymous:anonymous-agent");
|
||||
});
|
||||
|
||||
it("prefers explicit and discovered local-auth deployments before the default", () => {
|
||||
expect(resolveLocalAuthDeployment("anonymous:explicit-agent", "anonymous:local-agent")).toBe(
|
||||
"anonymous:explicit-agent",
|
||||
);
|
||||
expect(resolveLocalAuthDeployment(undefined, "anonymous:local-agent")).toBe(
|
||||
"anonymous:local-agent",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not inherit the generic CI Convex URL", () => {
|
||||
expect(
|
||||
resolveLocalAuthRunnerConfig({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const DEFAULT_CONVEX_URL = "http://127.0.0.1:3210";
|
||||
const DEFAULT_CONVEX_SITE_URL = "http://127.0.0.1:3211";
|
||||
export const DEFAULT_LOCAL_AUTH_CONVEX_DEPLOYMENT = "anonymous:anonymous-agent";
|
||||
const DEFAULT_PLAYWRIGHT_ARGS = ["--project=chromium", "e2e/local-auth"];
|
||||
const DEFAULT_PLAYWRIGHT_RETRIES = "1";
|
||||
|
||||
@@ -23,6 +24,13 @@ function withDefaultRetries(args: string[]) {
|
||||
return [`--retries=${DEFAULT_PLAYWRIGHT_RETRIES}`, ...args];
|
||||
}
|
||||
|
||||
export function resolveLocalAuthDeployment(
|
||||
configuredDeployment: string | undefined,
|
||||
fallbackDeployment: string | null | undefined,
|
||||
) {
|
||||
return configuredDeployment ?? fallbackDeployment ?? DEFAULT_LOCAL_AUTH_CONVEX_DEPLOYMENT;
|
||||
}
|
||||
|
||||
export function resolveLocalAuthRunnerConfig(
|
||||
env: RunnerEnv = process.env,
|
||||
argv: string[] = process.argv.slice(2),
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
import { createServer } from "node:net";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { resolveLocalAuthRunnerConfig } from "./playwright-local-auth-config";
|
||||
import {
|
||||
resolveLocalAuthDeployment,
|
||||
resolveLocalAuthRunnerConfig,
|
||||
} from "./playwright-local-auth-config";
|
||||
|
||||
const DEFAULT_CONVEX_DEPLOYMENT = "anonymous-agent";
|
||||
const DEFAULT_DEV_AUTH_CONVEX_DEPLOYMENT = "anonymous:anonymous-agent";
|
||||
const DEFAULT_PLAYWRIGHT_PORT = 4173;
|
||||
const DEFAULT_E2E_WORKER_TOKEN = "local-e2e-worker-token";
|
||||
@@ -468,8 +470,10 @@ async function main() {
|
||||
isolateLocalState();
|
||||
|
||||
const authKeys = buildAuthKeys();
|
||||
const deployment =
|
||||
runnerConfig.convexDeployment ?? readLocalDeployment() ?? DEFAULT_CONVEX_DEPLOYMENT;
|
||||
const deployment = resolveLocalAuthDeployment(
|
||||
runnerConfig.convexDeployment,
|
||||
readLocalDeployment(),
|
||||
);
|
||||
const e2eEnv: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
AUTH_GITHUB_ID: process.env.AUTH_GITHUB_ID ?? "local-dev",
|
||||
|
||||
Reference in New Issue
Block a user