mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 11:11:35 +00:00
* fix(android): use configured SDK for screenshot AVDs * fix(android): keep chat jump control clear of messages * fix(wear): ignore stale control completions * fix(android): advertise node permission changes * fix(android): select play flavor for benchmarks * test(android): decouple cron benchmark from copy * fix(android): install play variant in perf scripts * fix(wear): clear gateway control busy state * fix(android): surface voice e2e connection errors * test(android): cover common live node commands * fix(android): retain share parsing across recreation * fix(android): retain permission requests across recreation * fix(wear): bind realtime audio to talk attempts * fix(android): track active permission host * fix(wear): own gateway control busy state * fix(wear): admit realtime channels by attempt * fix(android): refresh permissions on top resume * fix(wear): serialize reconnect retirement with writes * chore(i18n): refresh native source inventory * fix(wear): retain pending audio through start RPC * fix(wear): negotiate realtime channel compatibility * test(wear): assert reconnect outcome without owner churn * fix(android): move settings prompt with active host * fix(wear): reject stale relay starts * chore(changelog): leave notes to release generation
169 lines
7.6 KiB
TypeScript
169 lines
7.6 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { readFileSync } from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const SCRIPT = "scripts/android-screenshots.sh";
|
|
const SCREENSHOT_FIXTURE =
|
|
"apps/android/app/src/main/java/ai/openclaw/app/AndroidScreenshotFixture.kt";
|
|
|
|
function runAndroidScreenshots(args: string[], env: NodeJS.ProcessEnv = {}) {
|
|
return spawnSync("bash", [SCRIPT, ...args], {
|
|
encoding: "utf8",
|
|
env: { ...process.env, ...env },
|
|
});
|
|
}
|
|
|
|
describe("android screenshots script", () => {
|
|
it("dry-runs with a normalized locale output path", () => {
|
|
const result = runAndroidScreenshots(["--dry-run", "--locale", "pt-BR"]);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout).toContain(
|
|
"apps/android/fastlane/metadata/android/pt-BR/images/phoneScreenshots",
|
|
);
|
|
expect(result.stdout).toContain(
|
|
"apps/android/fastlane/metadata/android/pt-BR/images/wearScreenshots",
|
|
);
|
|
expect(result.stdout).toContain(".artifacts/android-screenshots/latest/phone");
|
|
expect(result.stdout).toContain(".artifacts/android-screenshots/latest/wear");
|
|
expect(result.stdout).toContain("Android screenshot size: 1440x2560");
|
|
expect(result.stdout).toContain("Android screenshot size: 454x454");
|
|
expect(result.stdout).toContain("Screenshot AVD: OpenClaw_Screenshots_API36");
|
|
expect(result.stdout).toContain("Screenshot AVD: OpenClaw_Wear_Screenshots_API34");
|
|
expect(result.stdout).toContain("Screenshot device profile: pixel_2");
|
|
expect(result.stdout).toContain("Screenshot device profile: wearos_large_round");
|
|
expect(result.stdout).toContain("Scenes: home chat settings gateway voice-wake");
|
|
expect(result.stdout).toContain("Scenes: chat voice controls");
|
|
expect(result.stdout).not.toContain("connect chat voice screen settings");
|
|
expect(result.stdout).toContain("Dry run complete.");
|
|
});
|
|
|
|
it("keeps artifact cleanup inside the repository-owned evidence directory", () => {
|
|
const result = runAndroidScreenshots(["--dry-run"], {
|
|
ANDROID_SCREENSHOT_ARTIFACT_DIR: process.env.HOME,
|
|
});
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout).toContain(".artifacts/android-screenshots/latest");
|
|
expect(result.stdout).not.toContain(`Android screenshot artifacts: ${process.env.HOME}\n`);
|
|
});
|
|
|
|
it("waits for fixture content unique to the chat, settings, and gateway screens", () => {
|
|
const script = readFileSync(SCRIPT, "utf8");
|
|
const fixture = readFileSync(SCREENSHOT_FIXTURE, "utf8");
|
|
|
|
expect(script).toContain("chat) printf '%s\\n' \"Draft a short status update for the team.\"");
|
|
expect(script).not.toContain("chat) printf '%s\\n' \"Ready when you are\"");
|
|
expect(script).toContain('if [[ "$FORM_FACTOR" == "wear" ]]');
|
|
expect(script).toContain("voice) printf '%s\\n' \"Dictate\"");
|
|
expect(script).not.toContain("Ready to talk");
|
|
expect(fixture).toContain('"Draft a short status update for the team."');
|
|
expect(script).toContain("settings) printf '%s\\n' \"OpenClaw mobile\"");
|
|
expect(script).not.toContain("settings) printf '%s\\n' \"Settings\"");
|
|
expect(script).toContain(
|
|
"gateway) printf '%s\\n' \"Connection between this phone and OpenClaw.\"",
|
|
);
|
|
expect(script).not.toContain("gateway) printf '%s\\n' \"Add Gateway\"");
|
|
});
|
|
|
|
it("scales and restores display density with the screenshot size", () => {
|
|
const script = readFileSync(SCRIPT, "utf8");
|
|
|
|
expect(script).toContain('SCREENSHOT_SIZE="${ANDROID_SCREENSHOT_SIZE:-1440x2560}"');
|
|
expect(script).toContain('shell wm density "$SCREENSHOT_DENSITY"');
|
|
expect(script).toContain('shell wm density "$ORIGINAL_WM_DENSITY"');
|
|
expect(script).toContain("shell wm density reset");
|
|
});
|
|
|
|
it("pins the device timezone before rendering seeded timestamps", () => {
|
|
const script = readFileSync(SCRIPT, "utf8");
|
|
const requireEmulatorIndex = script.indexOf('require_emulator_device "$ADB_BIN" "$ADB_SERIAL"');
|
|
const stabilizeDeviceIndex = script.indexOf(
|
|
'stabilize_device_for_screenshots "$ADB_BIN" "$ADB_SERIAL"',
|
|
);
|
|
|
|
expect(script).toContain("shell cmd time_zone_detector set_auto_detection_enabled false");
|
|
expect(script).toContain("shell cmd alarm set-timezone UTC");
|
|
expect(script).toContain('shell cmd alarm set-timezone "$ORIGINAL_TIME_ZONE"');
|
|
expect(script).toContain(
|
|
'shell cmd time_zone_detector set_auto_detection_enabled "$ORIGINAL_AUTO_TIME_ZONE"',
|
|
);
|
|
expect(requireEmulatorIndex).toBeGreaterThan(-1);
|
|
expect(stabilizeDeviceIndex).toBeGreaterThan(requireEmulatorIndex);
|
|
});
|
|
|
|
it("dismisses the first-run Wear charging overlay before checking scene readiness", () => {
|
|
const script = readFileSync(SCRIPT, "utf8");
|
|
|
|
expect(script).toContain("com.google.android.wearable.sysui:id/charging_container");
|
|
expect(script).toContain("shell input keyevent 4");
|
|
});
|
|
|
|
it("provisions a retained no-cutout screenshot emulator by default", () => {
|
|
const script = readFileSync(SCRIPT, "utf8");
|
|
|
|
expect(script).toContain('DEFAULT_SCREENSHOT_AVD="OpenClaw_Screenshots_API36"');
|
|
expect(script).toContain('DEFAULT_SCREENSHOT_DEVICE_PROFILE="pixel_2"');
|
|
expect(script).toContain('DEFAULT_WEAR_SCREENSHOT_AVD="OpenClaw_Wear_Screenshots_API34"');
|
|
expect(script).toContain('DEFAULT_WEAR_SCREENSHOT_DEVICE_PROFILE="wearos_large_round"');
|
|
expect(script).toContain('GRADLE_ASSEMBLE_TASK=":wear:assembleDebug"');
|
|
expect(script).toContain('OUTPUT_TYPE="wearScreenshots"');
|
|
expect(script).toContain('ensure_screenshot_avd "$avd"');
|
|
expect(script).toContain('--device "$SCREENSHOT_DEVICE_PROFILE"');
|
|
expect(script).toContain("is not the screenshot AVD");
|
|
});
|
|
|
|
it("prefers avdmanager from the configured SDK over an unrelated PATH install", () => {
|
|
const script = readFileSync(SCRIPT, "utf8");
|
|
const functionStart = script.indexOf("avdmanager_bin() {");
|
|
const sdkLookup = script.indexOf(
|
|
'for sdk_root in "${ANDROID_HOME:-}" "${ANDROID_SDK_ROOT:-}" "$HOME/Library/Android/sdk"; do',
|
|
functionStart,
|
|
);
|
|
const pathLookup = script.indexOf("if command -v avdmanager", functionStart);
|
|
|
|
expect(functionStart).toBeGreaterThan(-1);
|
|
expect(sdkLookup).toBeGreaterThan(functionStart);
|
|
expect(pathLookup).toBeGreaterThan(sdkLookup);
|
|
});
|
|
|
|
it.each(["../escape", "en/US", ".hidden", "en..US", ""])(
|
|
"rejects locale path escapes before dry-run output: %j",
|
|
(locale) => {
|
|
const result = runAndroidScreenshots(["--dry-run", "--locale", locale]);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("Invalid Android screenshot locale");
|
|
expect(result.stderr).toContain("path separators and dot segments are not allowed");
|
|
expect(result.stdout).not.toContain("Android screenshot output:");
|
|
},
|
|
);
|
|
|
|
it("rejects screenshot dimensions outside Google Play's aspect-ratio limit", () => {
|
|
const result = runAndroidScreenshots(["--dry-run"], {
|
|
ANDROID_SCREENSHOT_SIZE: "1080x2424",
|
|
});
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("does not meet Google Play dimension and aspect-ratio limits");
|
|
});
|
|
|
|
it("requires a form factor when selecting one emulator explicitly", () => {
|
|
const result = runAndroidScreenshots(["--dry-run", "--avd", "custom"]);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
"--device and --avd require --form-factor phone or --form-factor wear",
|
|
);
|
|
});
|
|
|
|
it("requires one form factor when retaining an emulator", () => {
|
|
const result = runAndroidScreenshots(["--dry-run", "--keep-emulator"]);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
"--keep-emulator requires --form-factor phone or --form-factor wear",
|
|
);
|
|
});
|
|
});
|