补齐试玩回包审查要求的关键验收(#548)
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
绑定逐视口摘要中的状态、阻断原因与首个未通过断言 验证成功和失败结果均完整进入最终 MCP 回包 新增真实点击后未进入 playing 的浏览器场景并对照持久报告 收紧视觉未执行断言并补充长期验收要求
This commit is contained in:
@@ -4422,67 +4422,100 @@ pub(super) fn drain_analytics_test_writer(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn browser_gameplay_failure_reaches_model_with_both_viewports_and_screenshots() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let evidence =
|
||||
super::super::direct_validation::tests::failed_browser_evidence_fixture(temp.path());
|
||||
for viewport in &evidence.viewport_results {
|
||||
image::RgbaImage::new(2, 2)
|
||||
.save(&viewport.screenshot_path)
|
||||
.unwrap();
|
||||
}
|
||||
let receipt = super::super::direct_validation::browser_tool_result(
|
||||
temp.path(),
|
||||
&evidence,
|
||||
Some(crate::browser::BrowserPlaytestScenario::GenericV1),
|
||||
1,
|
||||
);
|
||||
assert!(evidence.viewport_results.iter().all(|result| result.passed));
|
||||
assert_eq!(receipt["passed"], false);
|
||||
let failure =
|
||||
bridge_validation_result(temp.path(), Ok(receipt.clone()), |report, screenshots| {
|
||||
BrowserPlaytestError::PlaytestNotPassed {
|
||||
fn browser_gameplay_results_reach_model_with_matching_viewports_and_screenshots() {
|
||||
for passed in [false, true] {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let evidence = if passed {
|
||||
super::super::direct_validation::tests::browser_evidence_fixture(temp.path())
|
||||
} else {
|
||||
super::super::direct_validation::tests::failed_browser_evidence_fixture(temp.path())
|
||||
};
|
||||
for viewport in &evidence.viewport_results {
|
||||
image::RgbaImage::new(2, 2)
|
||||
.save(&viewport.screenshot_path)
|
||||
.unwrap();
|
||||
}
|
||||
let receipt = super::super::direct_validation::browser_tool_result(
|
||||
temp.path(),
|
||||
&evidence,
|
||||
Some(crate::browser::BrowserPlaytestScenario::GenericV1),
|
||||
1,
|
||||
);
|
||||
assert!(evidence.viewport_results.iter().all(|result| result.passed));
|
||||
assert_eq!(receipt["passed"], passed);
|
||||
let outcome = bridge_validation_result(
|
||||
temp.path(),
|
||||
Ok(receipt.clone()),
|
||||
|report, screenshots| BrowserPlaytestError::PlaytestNotPassed {
|
||||
report,
|
||||
screenshots,
|
||||
}
|
||||
})
|
||||
.unwrap_err();
|
||||
let error = ToolCallError::from(&failure);
|
||||
assert_eq!(error.images.len(), 2);
|
||||
assert_eq!(error.error["PlaytestNotPassed"]["report"], receipt);
|
||||
assert!(error.error["PlaytestNotPassed"]
|
||||
.get("screenshots")
|
||||
.is_none());
|
||||
let state = direct_tool_bridge_state(temp.path().to_path_buf());
|
||||
let response =
|
||||
compose_direct_tool_outcome(&state, "agc_browser_playtest", "{}", false, Err(error));
|
||||
assert_eq!(response["isError"], true);
|
||||
let text = response["content"][0]["text"].as_str().unwrap();
|
||||
assert_eq!(
|
||||
response["content"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|block| block["type"] == "image")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
for (index, entry) in evidence.viewport_playtests.iter().enumerate() {
|
||||
let report = &receipt["gameplayResults"][index];
|
||||
},
|
||||
)
|
||||
.map_err(|failure| {
|
||||
let error = ToolCallError::from(&failure);
|
||||
assert_eq!(error.error["PlaytestNotPassed"]["report"], receipt);
|
||||
assert!(error.error["PlaytestNotPassed"]
|
||||
.get("screenshots")
|
||||
.is_none());
|
||||
error
|
||||
});
|
||||
let state = direct_tool_bridge_state(temp.path().to_path_buf());
|
||||
let response =
|
||||
compose_direct_tool_outcome(&state, "agc_browser_playtest", "{}", false, outcome);
|
||||
assert_eq!(response["isError"], !passed);
|
||||
let text = response["content"][0]["text"].as_str().unwrap();
|
||||
let model_receipt: Value =
|
||||
serde_json::from_str(&text[text.find('{').unwrap()..]).unwrap();
|
||||
assert_eq!(model_receipt, receipt);
|
||||
assert_eq!(
|
||||
report["assertions"],
|
||||
serde_json::to_value(&entry.result.assertions).unwrap()
|
||||
response["content"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|block| block["type"] == "image")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
assert_eq!(report["finalPhase"], "ready");
|
||||
assert_eq!(report["finalSequence"], index as u64);
|
||||
assert!(text.contains(&entry.result.diagnostics[0]));
|
||||
let summary = model_receipt["summary"].as_str().unwrap();
|
||||
for (index, entry) in evidence.viewport_playtests.iter().enumerate() {
|
||||
let report = &model_receipt["gameplayResults"][index];
|
||||
assert_eq!(
|
||||
report["viewport"],
|
||||
serde_json::to_value(entry.viewport).unwrap()
|
||||
);
|
||||
assert_eq!(report["passed"], entry.result.passed);
|
||||
assert_eq!(
|
||||
report["assertions"],
|
||||
serde_json::to_value(&entry.result.assertions).unwrap()
|
||||
);
|
||||
assert_eq!(report["finalPhase"], "ready");
|
||||
assert_eq!(
|
||||
report["finalSequence"],
|
||||
entry.result.final_sequence.unwrap()
|
||||
);
|
||||
let viewport = report["viewport"].as_str().unwrap();
|
||||
let line = summary
|
||||
.lines()
|
||||
.find(|line| line.starts_with(&format!("{viewport}: gameplayPassed=")))
|
||||
.unwrap();
|
||||
assert!(line.starts_with(&format!(
|
||||
"{viewport}: gameplayPassed={}",
|
||||
entry.result.passed
|
||||
)));
|
||||
if let Some(reason) = entry.result.diagnostics.first() {
|
||||
assert_eq!(report["diagnostics"][0], reason.as_str());
|
||||
assert!(line.contains(reason));
|
||||
}
|
||||
if let Some(assertion) = entry
|
||||
.result
|
||||
.assertions
|
||||
.iter()
|
||||
.find(|assertion| !assertion.passed)
|
||||
{
|
||||
assert!(line.contains(&assertion.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
let summary = receipt["summary"].as_str().unwrap();
|
||||
assert!(summary.contains("desktop: gameplayPassed=false"));
|
||||
assert!(summary.contains("mobile: gameplayPassed=false"));
|
||||
assert!(summary.contains("start-control-clicked"));
|
||||
assert!(summary.contains("start-phase-playing"));
|
||||
assert!(summary.contains("未执行"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -786,7 +786,9 @@ pub(super) mod tests {
|
||||
assert_eq!(receipt["passed"], mode != "missing-mobile");
|
||||
if mode == "visual" {
|
||||
assert_eq!(receipt["gameplayResults"], json!([]));
|
||||
assert!(summary.contains("未执行"));
|
||||
assert!(summary
|
||||
.lines()
|
||||
.any(|line| line.starts_with("玩法检查:未执行")));
|
||||
assert_eq!(receipt["coverage"], "visual-only");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1514,6 +1514,41 @@ async fn real_chrome_generic_playtest_pointer_events_keeps_unavailable_controls_
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires an installed Chrome/Chromium/Edge and explicit local browser execution"]
|
||||
async fn real_chrome_generic_playtest_reports_start_click_without_playing_phase() {
|
||||
let result = run_stable_generic_viewport_fixture(false, Some(
|
||||
r#"<button type="button" data-playtest-id="start" data-start-phase="ready">Start</button>"#,
|
||||
)).await;
|
||||
assert!(!result.passed);
|
||||
assert!(result
|
||||
.viewport_results
|
||||
.iter()
|
||||
.all(|viewport| viewport.passed));
|
||||
for entry in &result.viewport_playtests {
|
||||
let playtest = &entry.result;
|
||||
assert!(!playtest.passed);
|
||||
assert_eq!(playtest.final_phase, Some(BrowserPlaytestPhase::Ready));
|
||||
assert_eq!(playtest.final_sequence, Some(1));
|
||||
for (name, expected) in [
|
||||
("start-control-clicked", true),
|
||||
("start-phase-playing", false),
|
||||
] {
|
||||
assert_eq!(
|
||||
playtest
|
||||
.assertions
|
||||
.iter()
|
||||
.find(|assertion| assertion.name == name)
|
||||
.unwrap()
|
||||
.passed,
|
||||
expected
|
||||
);
|
||||
}
|
||||
let reason = &playtest.diagnostics[0];
|
||||
assert!(reason.contains("必须进入 playing"), "{reason}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_stable_generic_viewport_fixture(
|
||||
mobile_action_broken: bool,
|
||||
start_markup: Option<&str>,
|
||||
@@ -1556,7 +1591,7 @@ async fn run_stable_generic_viewport_fixture(
|
||||
};
|
||||
document.querySelector('[data-playtest-id="start"]').addEventListener('click', (event) => {
|
||||
if (event.isTrusted && state.phase === 'ready') {
|
||||
advance(() => { state.phase = 'playing'; });
|
||||
advance(() => { state.phase = event.currentTarget.dataset.startPhase || 'playing'; });
|
||||
}
|
||||
});
|
||||
const primary = document.querySelector('[data-playtest-id="primary-action"]');
|
||||
@@ -1656,6 +1691,17 @@ async fn run_stable_generic_viewport_fixture(
|
||||
{
|
||||
let projected = &receipt["gameplayResults"][index];
|
||||
assert_eq!(projected["viewport"], entry["viewport"]);
|
||||
let viewport = entry["viewport"].as_str().unwrap();
|
||||
let line = receipt["summary"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.lines()
|
||||
.find(|line| line.starts_with(&format!("{viewport}: gameplayPassed=")))
|
||||
.unwrap();
|
||||
assert!(line.starts_with(&format!(
|
||||
"{viewport}: gameplayPassed={}",
|
||||
entry["result"]["passed"]
|
||||
)));
|
||||
for field in [
|
||||
"passed",
|
||||
"assertions",
|
||||
@@ -1670,7 +1716,15 @@ async fn run_stable_generic_viewport_fixture(
|
||||
}
|
||||
if let Some(reason) = entry["result"]["diagnostics"][0].as_str() {
|
||||
assert_eq!(projected["diagnostics"][0], reason);
|
||||
assert!(receipt["summary"].as_str().unwrap().contains(reason));
|
||||
assert!(line.contains(reason));
|
||||
}
|
||||
if let Some(assertion) = entry["result"]["assertions"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|assertion| assertion["passed"] == false)
|
||||
{
|
||||
assert!(line.contains(assertion["name"].as_str().unwrap()));
|
||||
}
|
||||
}
|
||||
result
|
||||
|
||||
Reference in New Issue
Block a user