135 lines
5.3 KiB
JavaScript
135 lines
5.3 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("assert");
|
|
const fs = require("fs");
|
|
|
|
const cdpPort = process.env.CDP_PORT || "9222";
|
|
const requestedRoute = process.env.MUMU_ROUTE || "";
|
|
|
|
const getTargets = async () => (await fetch(`http://127.0.0.1:${cdpPort}/json/list`)).json();
|
|
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
const latestRouteTarget = (targets, routePrefix = "pages/") => targets
|
|
.filter((candidate) => candidate.type === "page" && candidate.title.startsWith(routePrefix))
|
|
.sort((left, right) => {
|
|
const leftIndex = Number(left.title.match(/\[(\d+)\]$/)?.[1] || 0);
|
|
const rightIndex = Number(right.title.match(/\[(\d+)\]$/)?.[1] || 0);
|
|
return rightIndex - leftIndex;
|
|
})[0];
|
|
|
|
const connect = async (target) => {
|
|
const socket = new WebSocket(target.webSocketDebuggerUrl);
|
|
await new Promise((resolve, reject) => {
|
|
socket.addEventListener("open", resolve, { once: true });
|
|
socket.addEventListener("error", reject, { once: true });
|
|
});
|
|
let id = 0;
|
|
const pending = new Map();
|
|
socket.addEventListener("message", (event) => {
|
|
const message = JSON.parse(event.data);
|
|
const request = pending.get(message.id);
|
|
if (!request) return;
|
|
pending.delete(message.id);
|
|
message.error ? request.reject(new Error(message.error.message)) : request.resolve(message.result);
|
|
});
|
|
const send = (method, params = {}) => new Promise((resolve, reject) => {
|
|
id += 1;
|
|
const requestId = id;
|
|
pending.set(requestId, { resolve, reject });
|
|
socket.send(JSON.stringify({ id: requestId, method, params }));
|
|
});
|
|
return { socket, send };
|
|
};
|
|
|
|
const run = async () => {
|
|
const configuredRoutes = JSON.parse(fs.readFileSync("pages.json", "utf8")).pages.map((page) => page.path);
|
|
const routes = requestedRoute ? [requestedRoute] : configuredRoutes;
|
|
assert.ok(routes.every((route) => configuredRoutes.includes(route)), "requested route is not configured in pages.json");
|
|
const failures = [];
|
|
let previousRoute = "";
|
|
for (const route of routes) {
|
|
await sleep(previousRoute === "pages/auth/a01-entry" ? 1600 : 350);
|
|
const activeTarget = latestRouteTarget(await getTargets());
|
|
if (!activeTarget) {
|
|
failures.push(`${route}: no active Uni page WebView`);
|
|
continue;
|
|
}
|
|
const active = await connect(activeTarget);
|
|
try {
|
|
void active.send("Runtime.evaluate", {
|
|
expression: `uni.reLaunch({ url: ${JSON.stringify(`/${route}`)} })`,
|
|
returnByValue: true,
|
|
}).catch(() => {});
|
|
} finally {
|
|
active.socket.close();
|
|
}
|
|
|
|
const deadline = Date.now() + 5000;
|
|
let routedTarget = null;
|
|
while (!routedTarget && Date.now() < deadline) {
|
|
routedTarget = latestRouteTarget(await getTargets(), `${route}[`);
|
|
if (!routedTarget) await sleep(100);
|
|
}
|
|
if (!routedTarget) {
|
|
await sleep(500);
|
|
const retryTarget = latestRouteTarget(await getTargets());
|
|
if (retryTarget) {
|
|
const retry = await connect(retryTarget);
|
|
try {
|
|
void retry.send("Runtime.evaluate", {
|
|
expression: `uni.reLaunch({ url: ${JSON.stringify(`/${route}`)} })`,
|
|
returnByValue: true,
|
|
}).catch(() => {});
|
|
} finally {
|
|
retry.socket.close();
|
|
}
|
|
const retryDeadline = Date.now() + 5000;
|
|
while (!routedTarget && Date.now() < retryDeadline) {
|
|
routedTarget = latestRouteTarget(await getTargets(), `${route}[`);
|
|
if (!routedTarget) await sleep(100);
|
|
}
|
|
}
|
|
}
|
|
if (!routedTarget) {
|
|
failures.push(`${route}: native route did not open`);
|
|
continue;
|
|
}
|
|
|
|
const routed = await connect(routedTarget);
|
|
const exceptions = [];
|
|
routed.socket.addEventListener("message", (event) => {
|
|
const message = JSON.parse(event.data);
|
|
if (message.method === "Runtime.exceptionThrown") exceptions.push(message.params.exceptionDetails.text);
|
|
});
|
|
try {
|
|
await routed.send("Runtime.enable");
|
|
let page = null;
|
|
while (Date.now() < deadline) {
|
|
const result = await routed.send("Runtime.evaluate", {
|
|
expression: "JSON.stringify({ textLength: document.body?.innerText?.trim().length || 0, loading: [...document.querySelectorAll('.app-loading, .uni-loading, .loading-spinner')].some((element) => { const style = getComputedStyle(element); const rect = element.getBoundingClientRect(); return style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity || 1) > 0 && rect.width > 0 && rect.height > 0; }) })",
|
|
returnByValue: true,
|
|
});
|
|
page = JSON.parse(result.result?.value || "{}");
|
|
if (page.textLength > 0 && !page.loading) break;
|
|
await sleep(100);
|
|
}
|
|
if (!page?.textLength || page.loading) {
|
|
failures.push(`${route}: page did not reach a stable rendered state`);
|
|
} else if (exceptions.length) {
|
|
failures.push(`${route}: ${exceptions.join("; ")}`);
|
|
} else {
|
|
process.stdout.write(`PASS ${route}\n`);
|
|
}
|
|
} finally {
|
|
routed.socket.close();
|
|
}
|
|
previousRoute = route;
|
|
}
|
|
if (failures.length) throw new Error(`MuMu native route audit failures:\n${failures.map((failure) => `- ${failure}`).join("\n")}`);
|
|
process.stdout.write(`MUMU-NATIVE-ROUTE-RUNTIME-SMOKE PASS routes=${routes.length}\n`);
|
|
};
|
|
|
|
run().catch((error) => {
|
|
process.stderr.write(`${error.stack || error.message}\n`);
|
|
process.exit(1);
|
|
});
|