android-midscene-automation 0.1.40 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# 更新记录
|
|
2
|
+
## v0.1.41
|
|
3
|
+
- 修复部分华为设备录屏启动时报编码器错误的问题:使用设备默认编码参数,视频流启动失败时降低分辨率和码率重试,并输出各次尝试的诊断日志。
|
|
4
|
+
|
|
2
5
|
## v0.1.40
|
|
6
|
+
- 开启录屏时,在 Appium 服务端日志结束后汇总所有录像片段的脚本名称和保存位置;未生成视频时明确提示。
|
|
7
|
+
- 录屏启动失败、录像不完整或保存失败时,日志明确提示对流程执行的影响并提供排查建议,保留原始错误信息。
|
|
3
8
|
- 回放录屏支持按连接脚本分段保存 MP4,返回父脚本继续执行时另存片段;历史结果可选择片段播放,HTML 报告按节点时间自动切换视频,删除历史结果时一并清理全部片段。
|
|
4
9
|
|
|
5
10
|
## v0.1.39
|
package/README.md
CHANGED
|
@@ -1229,7 +1229,12 @@ Linux: ~/.local/share/android-midscene-automation
|
|
|
1229
1229
|
|
|
1230
1230
|
# 项目更新记录
|
|
1231
1231
|
|
|
1232
|
+
## v0.1.41
|
|
1233
|
+
- 修复部分华为设备录屏启动时报编码器错误的问题:使用设备默认编码参数,视频流启动失败时降低分辨率和码率重试,并输出各次尝试的诊断日志。
|
|
1234
|
+
|
|
1232
1235
|
## v0.1.40
|
|
1236
|
+
- 开启录屏时,在 Appium 服务端日志结束后汇总所有录像片段的脚本名称和保存位置;未生成视频时明确提示。
|
|
1237
|
+
- 录屏启动失败、录像不完整或保存失败时,日志明确提示对流程执行的影响并提供排查建议,保留原始错误信息。
|
|
1233
1238
|
- 回放录屏支持按连接脚本分段保存 MP4,返回父脚本继续执行时另存片段;历史结果可选择片段播放,HTML 报告按节点时间自动切换视频,删除历史结果时一并清理全部片段。
|
|
1234
1239
|
|
|
1235
1240
|
## v0.1.39
|
package/package.json
CHANGED
|
@@ -1536,10 +1536,17 @@ export async function replayAppiumScript(
|
|
|
1536
1536
|
if (runOptions.recordVideo) {
|
|
1537
1537
|
if (scope.privacy.enabled) throw new Error('本次运行包含敏感变量,为避免泄露已禁止录屏,请关闭录制回放视频');
|
|
1538
1538
|
lines.push('正在启动后台录屏...');
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1539
|
+
try {
|
|
1540
|
+
recording = await startReplayVideo(targetDeviceId, signal, message => lines.push(message));
|
|
1541
|
+
replayContext.getStore()!.selectVideoScript = recording.selectScript;
|
|
1542
|
+
await recording.selectScript(script.id, script.name);
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
if (!isReplayStopped(error)) {
|
|
1545
|
+
lines.push('[录屏提示] 录屏启动失败,尚未执行任何流程节点。请检查设备连接、其他录屏或投屏占用,以及报告目录的写入权限和磁盘空间;也可关闭“录制回放视频”后重新回放。详细原因见下方错误日志。');
|
|
1546
|
+
}
|
|
1547
|
+
throw error;
|
|
1548
|
+
}
|
|
1549
|
+
lines.push('后台录屏已启动(MP4 / H.264 / 最高15fps,无音频)');
|
|
1543
1550
|
}
|
|
1544
1551
|
if (hasFlowSteps(script.steps)) lines.push('按流程图路径回放...');
|
|
1545
1552
|
await replayScriptSteps(sessionId, targetDeviceId, script.steps, lines, [script.id], {
|
|
@@ -1581,9 +1588,15 @@ export async function replayAppiumScript(
|
|
|
1581
1588
|
for (const [index, segment] of (video.segments || [video]).entries()) {
|
|
1582
1589
|
lines.push(`回放视频 ${index + 1}(${segment.scriptName || script.name})已保存:${segment.filePath}`);
|
|
1583
1590
|
}
|
|
1584
|
-
if (video.warning)
|
|
1591
|
+
if (video.warning) {
|
|
1592
|
+
lines.push(`录屏警告:${video.warning}`);
|
|
1593
|
+
lines.push('[录屏提示] 本次录像可能不完整,请结合节点日志和截图确认执行结果;需要完整录像时,请检查设备连接和录屏占用后重新回放。');
|
|
1594
|
+
}
|
|
1585
1595
|
}
|
|
1586
|
-
} catch (error) {
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
lines.push(`回放视频保存失败:${errorDetail(error)}`);
|
|
1598
|
+
lines.push('[录屏提示] 本次录像未能完整保存。请检查报告目录的写入权限、磁盘空间及设备连接;录像保存失败不等同于流程节点失败,请以节点执行日志为准。');
|
|
1599
|
+
}
|
|
1587
1600
|
}
|
|
1588
1601
|
let managedAppiumStopped = false;
|
|
1589
1602
|
if (signal?.aborted && managedAppium) {
|
|
@@ -1600,6 +1613,17 @@ export async function replayAppiumScript(
|
|
|
1600
1613
|
if (!managedAppiumStopped) await managedAppium.stop();
|
|
1601
1614
|
lines.push('----- Appium 服务端原始日志结束 -----');
|
|
1602
1615
|
}
|
|
1616
|
+
if (runOptions.recordVideo) {
|
|
1617
|
+
if (video) {
|
|
1618
|
+
for (const [index, segment] of (video.segments || [video]).entries()) {
|
|
1619
|
+
lines.push(`录屏位置 ${index + 1}(${segment.scriptName || script.name}):${segment.filePath}`);
|
|
1620
|
+
}
|
|
1621
|
+
} else {
|
|
1622
|
+
lines.push(scope.privacy.enabled
|
|
1623
|
+
? '录屏位置:无(敏感变量保护,本次不保留录像)'
|
|
1624
|
+
: '录屏位置:无(未生成可用视频,请查看上方录屏提示)');
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1603
1627
|
}
|
|
1604
1628
|
|
|
1605
1629
|
if (signal?.aborted && !stopped) {
|
|
@@ -33,15 +33,33 @@ export async function isFinalizedMp4(path: string) {
|
|
|
33
33
|
} finally { await file.close(); }
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export async function startReplayVideo(deviceId: string, signal?: AbortSignal) {
|
|
36
|
+
export async function startReplayVideo(deviceId: string, signal?: AbortSignal, onLog?: (message: string) => void) {
|
|
37
37
|
signal?.throwIfAborted();
|
|
38
38
|
const dir = appDataPath(loadConfig().runtime.reportOutputPath.trim() || 'output');
|
|
39
39
|
await mkdir(dir, { recursive: true });
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const failures: string[] = [];
|
|
41
|
+
for (const profile of [
|
|
42
|
+
{ label: '标准(1280 / 2Mbps)', maxSize: 1280, videoBitRate: 2000000 },
|
|
43
|
+
{ label: '兼容(800 / 1Mbps)', maxSize: 800, videoBitRate: 1000000 },
|
|
44
|
+
]) {
|
|
45
|
+
signal?.throwIfAborted();
|
|
46
|
+
const fileName = `replay-${Date.now()}-${randomUUID()}.mp4`;
|
|
47
|
+
const filePath = join(dir, fileName);
|
|
48
|
+
const startupSignal = AbortSignal.any([...(signal ? [signal] : []), AbortSignal.timeout(15000)]);
|
|
49
|
+
try {
|
|
50
|
+
onLog?.(`录屏启动:${profile.label},使用设备默认编码参数`);
|
|
51
|
+
const source = await openRecordingSource(deviceId, startupSignal, profile);
|
|
52
|
+
return await recordVideoStream(source, filePath, fileName, startupSignal);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
signal?.throwIfAborted();
|
|
55
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
56
|
+
failures.push(`${profile.label}:${detail}`);
|
|
57
|
+
// 仅编码/视频流启动异常才降级,ADB、磁盘、服务端版本错误直接保留原错误。
|
|
58
|
+
if (!/录屏视频流|关键帧|codec|encod|capture|timeout|timed out/i.test(detail)) throw error;
|
|
59
|
+
onLog?.(`录屏启动失败:${failures.at(-1)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`录屏兼容重试失败,尚未执行流程节点。请检查设备编码器或其他录屏占用。\n${failures.join('\n')}`);
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
// 独立于连接层,便于用真实 H.264 数据验证封装、断流和终止收尾。
|
|
@@ -24,7 +24,11 @@ function adbCommand(args: string[], signal?: AbortSignal) {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export
|
|
27
|
+
export type RecordingProfile = { label: string; maxSize: number; videoBitRate: number };
|
|
28
|
+
|
|
29
|
+
export async function openRecordingSource(deviceId: string, signal: AbortSignal, profile: RecordingProfile = {
|
|
30
|
+
label: '标准', maxSize: 1280, videoBitRate: 2000000,
|
|
31
|
+
}) {
|
|
28
32
|
const require = createRequire(import.meta.url);
|
|
29
33
|
const bin = join(dirname(require.resolve('@midscene/android-playground/package.json')), 'bin');
|
|
30
34
|
const version = (await readFile(join(bin, 'scrcpy-server.version'), 'utf8')).trim();
|
|
@@ -52,8 +56,9 @@ export async function openRecordingSource(deviceId: string, signal: AbortSignal)
|
|
|
52
56
|
const options = new AdbScrcpyOptions3_3_3({
|
|
53
57
|
scid: Math.floor(Math.random() * 0x7fffffff).toString(16).padStart(8, '0'),
|
|
54
58
|
audio: false, control: false, videoCodec: 'h264', sendFrameMeta: true,
|
|
55
|
-
maxSize:
|
|
56
|
-
|
|
59
|
+
maxSize: profile.maxSize, maxFps: 15, videoBitRate: profile.videoBitRate,
|
|
60
|
+
// 不强制 profile/max-bframes 等厂商兼容性差的参数,交给设备选择。
|
|
61
|
+
captureOrientation: '@',
|
|
57
62
|
});
|
|
58
63
|
client = await waitForVideoTask(AdbScrcpyClient.start(adb, remotePath, options).then(value => {
|
|
59
64
|
if (closing || signal.aborted) { void value.close(); throw signal.reason || new Error('录屏已取消'); }
|
|
@@ -75,6 +80,6 @@ export async function openRecordingSource(deviceId: string, signal: AbortSignal)
|
|
|
75
80
|
return { stream: video.stream, close, diagnostics: () => logs };
|
|
76
81
|
} catch (error) {
|
|
77
82
|
await close();
|
|
78
|
-
throw error;
|
|
83
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}${logs}`, { cause: error });
|
|
79
84
|
}
|
|
80
85
|
}
|