mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
* fix(ci): skip unrelated required checks cleanly Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable. * fix(ci): gate changes to install patching script * fix(ci): route cross-package test contracts narrowly * fix(ci): keep routed contracts merge-blocking * fix(server): exclude nested test utilities from builds * fix(ci): cover app-owned native and browser domains * refactor(ci): align tests with runtime domains Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check. * fix(ci): keep routing and desktop suites isolated The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs. * fix(ci): isolate desktop e2e runtime * perf(test): isolate Playwright workers * perf(test): parallelize Playwright within specs * fix(test): keep Playwright file scheduling * fix(test): share dynamic Metro fixture * fix(test): preserve own-host restart fixtures * fix(ci): preserve routed contracts after rebase * Stabilize Playwright wheel-scroll assertions
155 lines
4.1 KiB
TypeScript
155 lines
4.1 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
import type { ProviderUsage } from "@getpaseo/protocol/messages";
|
|
import { daemonWsRoutePattern } from "./daemon-port";
|
|
|
|
interface ProviderUsageFixturePayload {
|
|
fetchedAt: string;
|
|
providers: ProviderUsage[];
|
|
}
|
|
|
|
export interface ProviderUsageFixture {
|
|
requestCount(): number;
|
|
waitForRequestCount(count: number): Promise<void>;
|
|
}
|
|
|
|
type WebSocketMessage = string | Buffer;
|
|
|
|
function parseJson(message: WebSocketMessage): unknown {
|
|
const raw = typeof message === "string" ? message : message.toString("utf8");
|
|
try {
|
|
return JSON.parse(raw);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getSessionMessage(message: WebSocketMessage): Record<string, unknown> | null {
|
|
const envelope = parseJson(message);
|
|
if (!envelope || typeof envelope !== "object") {
|
|
return null;
|
|
}
|
|
const maybeEnvelope = envelope as { type?: unknown; message?: unknown };
|
|
if (maybeEnvelope.type !== "session" || !maybeEnvelope.message) {
|
|
return null;
|
|
}
|
|
if (typeof maybeEnvelope.message !== "object") {
|
|
return null;
|
|
}
|
|
return maybeEnvelope.message as Record<string, unknown>;
|
|
}
|
|
|
|
function withProviderUsageFeature(message: WebSocketMessage): string | null {
|
|
const envelope = parseJson(message);
|
|
if (!envelope || typeof envelope !== "object") {
|
|
return null;
|
|
}
|
|
const maybeEnvelope = envelope as {
|
|
type?: unknown;
|
|
message?: {
|
|
type?: unknown;
|
|
payload?: Record<string, unknown>;
|
|
};
|
|
};
|
|
const payload = maybeEnvelope.message?.payload;
|
|
if (
|
|
maybeEnvelope.type !== "session" ||
|
|
maybeEnvelope.message?.type !== "status" ||
|
|
payload?.status !== "server_info"
|
|
) {
|
|
return null;
|
|
}
|
|
return JSON.stringify({
|
|
...maybeEnvelope,
|
|
message: {
|
|
...maybeEnvelope.message,
|
|
payload: {
|
|
...payload,
|
|
features: {
|
|
...(typeof payload.features === "object" && payload.features !== null
|
|
? payload.features
|
|
: {}),
|
|
providerUsageList: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function installProviderUsageFixture(
|
|
page: Page,
|
|
payloads: ProviderUsageFixturePayload[],
|
|
): Promise<ProviderUsageFixture> {
|
|
let requests = 0;
|
|
const waiters: Array<{ count: number; resolve: () => void }> = [];
|
|
|
|
function notifyWaiters() {
|
|
for (const waiter of waiters.splice(0)) {
|
|
if (requests >= waiter.count) {
|
|
waiter.resolve();
|
|
} else {
|
|
waiters.push(waiter);
|
|
}
|
|
}
|
|
}
|
|
|
|
function payloadForRequest(): ProviderUsageFixturePayload {
|
|
const index = Math.min(requests - 1, payloads.length - 1);
|
|
const payload = payloads[index];
|
|
if (!payload) {
|
|
throw new Error("Provider usage fixture requires at least one payload.");
|
|
}
|
|
return payload;
|
|
}
|
|
|
|
await page.routeWebSocket(daemonWsRoutePattern(), (ws) => {
|
|
const server = ws.connectToServer();
|
|
|
|
ws.onMessage((message) => {
|
|
const sessionMessage = getSessionMessage(message);
|
|
if (sessionMessage?.type === "provider.usage.list.request") {
|
|
requests += 1;
|
|
const requestId = sessionMessage.requestId;
|
|
if (typeof requestId !== "string") {
|
|
throw new Error("provider.usage.list.request missing requestId");
|
|
}
|
|
const payload = payloadForRequest();
|
|
notifyWaiters();
|
|
ws.send(
|
|
JSON.stringify({
|
|
type: "session",
|
|
message: {
|
|
type: "provider.usage.list.response",
|
|
payload: {
|
|
requestId,
|
|
fetchedAt: payload.fetchedAt,
|
|
providers: payload.providers,
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
return;
|
|
}
|
|
server.send(message);
|
|
});
|
|
|
|
server.onMessage((message) => {
|
|
const serverInfo = typeof message === "string" ? withProviderUsageFeature(message) : null;
|
|
ws.send(serverInfo ?? message);
|
|
});
|
|
});
|
|
|
|
return {
|
|
requestCount() {
|
|
return requests;
|
|
},
|
|
waitForRequestCount(count: number) {
|
|
if (requests >= count) {
|
|
return Promise.resolve();
|
|
}
|
|
return new Promise<void>((resolve) => {
|
|
waiters.push({ count, resolve });
|
|
});
|
|
},
|
|
};
|
|
}
|