mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
fix(android): show conversation in screenshot fixture (#111000)
* fix(android): seed screenshot chat history * fix(android): update screenshot chat readiness * fix(android): use visible screenshot chat marker * style(android): format screenshot script test * fix(android): pin screenshot timezone * fix(android): restore screenshot timezone
This commit is contained in:
@@ -191,13 +191,44 @@ internal object AndroidScreenshotFixture {
|
||||
buildJsonObject {
|
||||
put("sessionId", JsonPrimitive("screenshot-session"))
|
||||
put("thinkingLevel", JsonPrimitive("low"))
|
||||
put("messages", buildJsonArray {})
|
||||
put(
|
||||
"messages",
|
||||
buildJsonArray {
|
||||
add(chatMessage("user", "What is blocking the Android release?", 1_783_555_020_000))
|
||||
add(
|
||||
chatMessage(
|
||||
"assistant",
|
||||
"Two review threads are still open on the release branch, and the localization sync needs one more pass. " +
|
||||
"Once those land, the changelog draft is ready for review and the tag can go out.",
|
||||
1_783_555_080_000,
|
||||
),
|
||||
)
|
||||
add(chatMessage("user", "Summarize the open review feedback for me.", 1_783_555_140_000))
|
||||
add(
|
||||
chatMessage(
|
||||
"assistant",
|
||||
"The main thread asks for a regression test around session restore, and the second one wants the new " +
|
||||
"config key documented before merge. Both are small; I can draft patches for each if you want.",
|
||||
1_783_555_200_000,
|
||||
),
|
||||
)
|
||||
add(chatMessage("user", "Draft a short status update for the team.", 1_783_555_260_000))
|
||||
add(
|
||||
chatMessage(
|
||||
"assistant",
|
||||
"The Android release is close. Two review follow-ups and one localization pass remain; once those land, " +
|
||||
"the changelog can be reviewed and the tag can go out.",
|
||||
1_783_555_320_000,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
put(
|
||||
"sessionInfo",
|
||||
buildJsonObject {
|
||||
put("key", JsonPrimitive(mainSessionKey))
|
||||
put("displayName", JsonPrimitive("New chat"))
|
||||
put("updatedAt", JsonPrimitive(1_783_555_200_000))
|
||||
put("updatedAt", JsonPrimitive(1_783_555_320_000))
|
||||
put("unread", JsonPrimitive(false))
|
||||
put("modelProvider", JsonPrimitive("openai"))
|
||||
put("model", JsonPrimitive("gpt-5.2"))
|
||||
@@ -206,6 +237,16 @@ internal object AndroidScreenshotFixture {
|
||||
)
|
||||
}.toString()
|
||||
|
||||
private fun chatMessage(
|
||||
role: String,
|
||||
content: String,
|
||||
timestamp: Long,
|
||||
) = buildJsonObject {
|
||||
put("role", JsonPrimitive(role))
|
||||
put("content", JsonPrimitive(content))
|
||||
put("timestamp", JsonPrimitive(timestamp))
|
||||
}
|
||||
|
||||
private fun sessionList(): String =
|
||||
buildJsonObject {
|
||||
put(
|
||||
|
||||
@@ -67,6 +67,50 @@ class AndroidScreenshotFixtureTest {
|
||||
assertEquals("Play publish blocked", parsedCronRuns.last().error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun providesDeterministicChatHistory() {
|
||||
val messages =
|
||||
json
|
||||
.parseToJsonElement(AndroidScreenshotFixture.request("chat.history", null))
|
||||
.jsonObject["messages"]
|
||||
?.jsonArray
|
||||
.orEmpty()
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
listOf("user", "What is blocking the Android release?", "1783555020000"),
|
||||
listOf(
|
||||
"assistant",
|
||||
"Two review threads are still open on the release branch, and the localization sync needs one more pass. " +
|
||||
"Once those land, the changelog draft is ready for review and the tag can go out.",
|
||||
"1783555080000",
|
||||
),
|
||||
listOf("user", "Summarize the open review feedback for me.", "1783555140000"),
|
||||
listOf(
|
||||
"assistant",
|
||||
"The main thread asks for a regression test around session restore, and the second one wants the new " +
|
||||
"config key documented before merge. Both are small; I can draft patches for each if you want.",
|
||||
"1783555200000",
|
||||
),
|
||||
listOf("user", "Draft a short status update for the team.", "1783555260000"),
|
||||
listOf(
|
||||
"assistant",
|
||||
"The Android release is close. Two review follow-ups and one localization pass remain; once those land, " +
|
||||
"the changelog can be reviewed and the tag can go out.",
|
||||
"1783555320000",
|
||||
),
|
||||
),
|
||||
messages.map { message ->
|
||||
val fields = message.jsonObject
|
||||
listOf(
|
||||
fields["role"]?.jsonPrimitive?.content,
|
||||
fields["content"]?.jsonPrimitive?.content,
|
||||
fields["timestamp"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsUnexpectedGatewayCalls() {
|
||||
val error =
|
||||
|
||||
@@ -47,6 +47,9 @@ DISPLAY_OVERRIDDEN=0
|
||||
ORIGINAL_WM_SIZE=""
|
||||
ORIGINAL_WM_DENSITY=""
|
||||
SCREENSHOT_DENSITY=""
|
||||
TIMEZONE_OVERRIDDEN=0
|
||||
ORIGINAL_AUTO_TIME_ZONE=""
|
||||
ORIGINAL_TIME_ZONE=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
@@ -151,6 +154,18 @@ restore_device_display() {
|
||||
fi
|
||||
}
|
||||
|
||||
restore_device_timezone() {
|
||||
if [[ "$TIMEZONE_OVERRIDDEN" != "1" || -z "${ADB_BIN:-}" || -z "${ADB_SERIAL:-}" ]]; then
|
||||
return
|
||||
fi
|
||||
if [[ -n "$ORIGINAL_TIME_ZONE" ]]; then
|
||||
"$ADB_BIN" -s "$ADB_SERIAL" shell cmd alarm set-timezone "$ORIGINAL_TIME_ZONE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [[ "$ORIGINAL_AUTO_TIME_ZONE" == "true" || "$ORIGINAL_AUTO_TIME_ZONE" == "false" ]]; then
|
||||
"$ADB_BIN" -s "$ADB_SERIAL" shell cmd time_zone_detector set_auto_detection_enabled "$ORIGINAL_AUTO_TIME_ZONE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_emulator_log() {
|
||||
if [[ -n "$EMULATOR_LOG" && -f "$EMULATOR_LOG" ]]; then
|
||||
rm -f "$EMULATOR_LOG"
|
||||
@@ -159,6 +174,7 @@ cleanup_emulator_log() {
|
||||
|
||||
cleanup() {
|
||||
restore_device_display
|
||||
restore_device_timezone
|
||||
cleanup_started_emulator
|
||||
cleanup_emulator_log
|
||||
}
|
||||
@@ -333,6 +349,22 @@ wait_for_explicit_device() {
|
||||
stabilize_device_for_screenshots() {
|
||||
local adb="$1"
|
||||
local serial="$2"
|
||||
ORIGINAL_AUTO_TIME_ZONE="$("$adb" -s "$serial" shell cmd time_zone_detector is_auto_detection_enabled 2>/dev/null | tr -d '\r')"
|
||||
ORIGINAL_TIME_ZONE="$("$adb" -s "$serial" shell getprop persist.sys.timezone 2>/dev/null | tr -d '\r')"
|
||||
if [[ "$ORIGINAL_AUTO_TIME_ZONE" != "true" && "$ORIGINAL_AUTO_TIME_ZONE" != "false" ]]; then
|
||||
echo "Could not determine emulator automatic timezone setting." >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ -z "$ORIGINAL_TIME_ZONE" ]]; then
|
||||
echo "Could not determine emulator timezone." >&2
|
||||
return 1
|
||||
fi
|
||||
# Arm cleanup before the first mutation; restoring an unchanged snapshot is safe.
|
||||
TIMEZONE_OVERRIDDEN=1
|
||||
# Seeded chat timestamps use the device timezone, so disable detection before
|
||||
# pinning UTC to avoid inheriting the runner's locale.
|
||||
"$adb" -s "$serial" shell cmd time_zone_detector set_auto_detection_enabled false >/dev/null
|
||||
"$adb" -s "$serial" shell cmd alarm set-timezone UTC >/dev/null
|
||||
"$adb" -s "$serial" shell settings put global window_animation_scale 0 >/dev/null 2>&1 || true
|
||||
"$adb" -s "$serial" shell settings put global transition_animation_scale 0 >/dev/null 2>&1 || true
|
||||
"$adb" -s "$serial" shell settings put global animator_duration_scale 0 >/dev/null 2>&1 || true
|
||||
@@ -404,7 +436,6 @@ boot_emulator() {
|
||||
|
||||
serial="$(wait_for_single_device "$adb")"
|
||||
wait_for_boot_completed "$adb" "$serial"
|
||||
stabilize_device_for_screenshots "$adb" "$serial"
|
||||
ADB_SERIAL="$serial"
|
||||
}
|
||||
|
||||
@@ -416,7 +447,6 @@ resolve_device() {
|
||||
|
||||
if [[ -n "$DEVICE" ]]; then
|
||||
wait_for_explicit_device "$adb" "$DEVICE"
|
||||
stabilize_device_for_screenshots "$adb" "$DEVICE"
|
||||
ADB_SERIAL="$DEVICE"
|
||||
return
|
||||
fi
|
||||
@@ -429,7 +459,6 @@ resolve_device() {
|
||||
echo "Stop it so the script can boot '${AVD}', or pass --device '${devices}' to override the no-cutout profile." >&2
|
||||
return 1
|
||||
fi
|
||||
stabilize_device_for_screenshots "$adb" "$devices"
|
||||
ADB_SERIAL="$devices"
|
||||
return
|
||||
fi
|
||||
@@ -452,7 +481,9 @@ latest_play_debug_apk() {
|
||||
scene_ready_text() {
|
||||
case "$1" in
|
||||
home) printf '%s\n' "Overview" ;;
|
||||
chat) printf '%s\n' "Ready when you are" ;;
|
||||
# The screenshot fixture seeds chat history and restores at the latest user
|
||||
# turn, so wait for that visible anchor instead of empty-chat copy.
|
||||
chat) printf '%s\n' "Draft a short status update for the team." ;;
|
||||
voice) printf '%s\n' "Ready to talk" ;;
|
||||
settings) printf '%s\n' "OpenClaw mobile" ;;
|
||||
voice-wake) printf '%s\n' "Wake listener" ;;
|
||||
@@ -568,6 +599,7 @@ fi
|
||||
ADB_BIN="$(adb_bin)"
|
||||
resolve_device "$ADB_BIN"
|
||||
require_emulator_device "$ADB_BIN" "$ADB_SERIAL"
|
||||
stabilize_device_for_screenshots "$ADB_BIN" "$ADB_SERIAL"
|
||||
configure_screenshot_display "$ADB_BIN" "$ADB_SERIAL"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
rm -f "$OUTPUT_DIR"/*.png "$OUTPUT_DIR"/*.jpg "$OUTPUT_DIR"/*.jpeg
|
||||
|
||||
@@ -3,6 +3,8 @@ 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], {
|
||||
@@ -38,9 +40,13 @@ describe("android screenshots script", () => {
|
||||
expect(result.stdout).not.toContain(`Android screenshot artifacts: ${process.env.HOME}\n`);
|
||||
});
|
||||
|
||||
it("waits for content unique to the settings and gateway screens", () => {
|
||||
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(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(
|
||||
@@ -58,6 +64,23 @@ describe("android screenshots script", () => {
|
||||
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("provisions a retained no-cutout screenshot emulator by default", () => {
|
||||
const script = readFileSync(SCRIPT, "utf8");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user