fix: stabilize CI smoke and local-auth gates (#2850)

This commit is contained in:
Jesse Merhi
2026-06-25 11:54:10 +10:00
committed by GitHub
parent 2deb1b75e9
commit 2d2c1e7a72
8 changed files with 91 additions and 14 deletions
+3
View File
@@ -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" });
+41
View File
@@ -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();
});
}
+10 -1
View File
@@ -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()}`);
});
+2 -7
View File
@@ -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
View File
@@ -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",
+18 -1
View File
@@ -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({
+8
View File
@@ -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),
+8 -4
View File
@@ -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",