feat: release 2.2.0 with PC-only tests and execution reliability fixes

Unify task scheduling and queue state, fix Android input and observation recovery, preserve screenshot paths, and match final review criteria accurately. Add collapsible copyable results and publish the complete 2.1.0 upgrade notes. Validation: 3189 backend tests, 326 frontend tests, 24 client tests, production build and quality gates.
This commit is contained in:
chenmingxuan committed 2026-10-27 01:13:41 +08:00
1 parent fac4ccb760
commit be6e8c2abd
79 files changed
+2263 -277

No files matched your search

+14 -1
View File
@@ -51,7 +51,9 @@ class TaskOptions(BaseModel):
action_failure_attempts: int | None = Field(default=None, ge=1, le=10)
worker_cleanup_timeout_seconds: float | None = Field(default=None, gt=0, le=300)
plugins: list[PluginSelection] | None = None
execution_target: ExecutionTarget | None = None
execution_target: ExecutionTarget | None = Field(
default=None, description="android (default), computer (PC only, no Android), or mixed"
)
@field_serializer("recording", when_used="unless-none")
def serialize_recording(self, value: RecordingConfig) -> dict:
@@ -117,6 +119,17 @@ def template() -> bytes:
),
]
)
help_sheet.append(
[
"示例-PC-001",
"电脑桌面",
"浏览器检查",
"仅 PC 测试",
"已启用并授权提供画面观察的电脑插件,无需手机或模拟器在线",
"打开电脑浏览器,访问指定网页并核对实际结果。",
json.dumps({"profile": "flash", "execution_target": "computer"}, ensure_ascii=False),
]
)
for column in range(1, 8):
help_sheet.column_dimensions[get_column_letter(column)].width = 70 if column > 5 else 26
for row in help_sheet:
@@ -346,7 +346,7 @@ class TaskQueueService:
@classmethod
def _concurrency_limit(cls) -> int:
"""Return 0 for per-device concurrency, or the global task limit."""
"""Return the shared global single-task limit, including computer targets."""
return DeviceExecutionLock.resolve_env_concurrency()
@classmethod
@@ -354,10 +354,8 @@ class TaskQueueService:
"""Persistent dispatcher scheduling pending tasks onto devices.
Scans the queue and launches each eligible task as an independent
coroutine. Admission is governed by _concurrency_limit(): 0 admits one
task per device so distinct devices execute concurrently, N>=1 admits
at most N tasks across all devices. Per-device FIFO ordering and
cross-process mutual exclusion remain enforced by DeviceExecutionLock
coroutine. Admission allows one executing task across all targets.
Global FIFO ordering and cross-process mutual exclusion are enforced by DeviceExecutionLock
inside each worker process.
"""
print(
@@ -436,6 +434,8 @@ class TaskQueueService:
target = cls._task_target(item)
resources = set(item.get("plugin_resources", []))
if resources & busy_resources:
if limit == 1:
return
continue
if limit == 0:
# A task without a resolved device may bind to any serial, so it
@@ -451,6 +451,8 @@ class TaskQueueService:
continue
elif limit >= 1 and device is not None and target.lock_key in busy_devices:
# A second worker for this device would wait on its lock.
if limit == 1:
return
continue
item["status"] = "running"