Files
skill-lib/tests/zhihu_skill_test.rs
木炎 6aad2ce48e feat: restore zhihu browser skills
Reconnect the recovered Zhihu skill flows to the live browser runtime and resolve their resources relative to the executable so they work outside the repo root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 14:29:38 +08:00

358 lines
11 KiB
Rust

mod common;
use std::sync::Arc;
use std::time::Duration;
use common::MockTransport;
use sgclaw::pipe::{Action, AgentMessage, BrowserMessage, BrowserPipeTool, Timing};
use sgclaw::security::MacPolicy;
use sgclaw::skill::zhihu::{execute, load_flow, ZhihuWriteRequest};
fn test_policy() -> MacPolicy {
MacPolicy::from_json_str(
r#"{
"version": "1.0",
"domains": { "allowed": ["www.zhihu.com", "zhuanlan.zhihu.com"] },
"pipe_actions": {
"allowed": ["click", "type", "navigate", "getText", "getHtml", "waitForSelector", "scrollTo"],
"blocked": []
}
}"#,
)
.unwrap()
}
fn response(seq: u64, data: serde_json::Value) -> BrowserMessage {
BrowserMessage::Response {
seq,
success: true,
data,
aom_snapshot: vec![],
timing: Timing {
queue_ms: 1,
exec_ms: 10,
},
}
}
#[test]
fn load_flow_preserves_validated_zhihu_literals() {
let flow = load_flow().unwrap();
assert_eq!(flow.entry_url, "https://www.zhihu.com/creator");
assert_eq!(flow.editor_url, "https://zhuanlan.zhihu.com/write");
assert_eq!(flow.literals["write_entry_text"], "写文章");
assert_eq!(flow.literals["publish_confirm_text"], "确认发布");
assert_eq!(
flow.literals["title_placeholder"],
"请输入标题(最多 100 个字)"
);
assert_eq!(
flow.selectors["creator_write_entry"],
"div.css-1q62b6s > div.css-byu4by"
);
assert_eq!(
flow.selectors["publish_confirm_button"],
"div[role='dialog'] button.Button--primary.Button--blue"
);
}
#[test]
fn zhihu_skill_stops_before_publish_when_publish_is_false() {
let transport = Arc::new(MockTransport::new(vec![
response(
1,
serde_json::json!({ "url": "https://www.zhihu.com/creator" }),
),
response(
2,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(
3,
serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(4, serde_json::json!({ "typed": true })),
response(5, serde_json::json!({ "typed": true })),
]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
let result = execute(
transport.as_ref(),
&browser_tool,
ZhihuWriteRequest {
title: "自动发文能力测试".to_string(),
body: "第一段\n\n第二段".to_string(),
publish: false,
},
)
.unwrap();
let sent = transport.sent_messages();
assert_eq!(result.summary, "知乎文章草稿已填充:自动发文能力测试");
assert_eq!(sent.len(), 10);
assert!(matches!(
&sent[5],
AgentMessage::Command { seq, action, .. }
if *seq == 3 && action == &Action::WaitForSelector
));
assert!(matches!(
&sent[9],
AgentMessage::Command { seq, action, .. }
if *seq == 5 && action == &Action::Type
));
}
#[test]
fn zhihu_skill_publishes_only_after_confirming_dialog_title_and_final_url() {
let transport = Arc::new(MockTransport::new(vec![
response(
1,
serde_json::json!({ "url": "https://www.zhihu.com/creator" }),
),
response(
2,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(
3,
serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(4, serde_json::json!({ "typed": true })),
response(5, serde_json::json!({ "typed": true })),
response(6, serde_json::json!({ "scrolled": true })),
response(
7,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(8, serde_json::json!({ "ready": true })),
response(
9,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/p/123456" }),
),
response(
10,
serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/p/123456" }),
),
response(
11,
serde_json::json!({ "text": "自动发文能力测试", "url": "https://zhuanlan.zhihu.com/p/123456" }),
),
]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
let result = execute(
transport.as_ref(),
&browser_tool,
ZhihuWriteRequest {
title: "自动发文能力测试".to_string(),
body: "第一段\n\n第二段".to_string(),
publish: true,
},
)
.unwrap();
let sent = transport.sent_messages();
assert_eq!(
result.summary,
"知乎文章已发布:自动发文能力测试 (https://zhuanlan.zhihu.com/p/123456)"
);
assert_eq!(
result.final_url.as_deref(),
Some("https://zhuanlan.zhihu.com/p/123456")
);
assert!(result.published);
assert_eq!(sent.len(), 22);
assert!(matches!(
&sent[11],
AgentMessage::Command { seq, action, .. }
if *seq == 6 && action == &Action::ScrollTo
));
assert!(matches!(
&sent[21],
AgentMessage::Command { seq, action, .. }
if *seq == 11 && action == &Action::GetText
));
}
#[test]
fn zhihu_skill_accepts_edit_url_as_published_article_url() {
let transport = Arc::new(MockTransport::new(vec![
response(
1,
serde_json::json!({ "url": "https://www.zhihu.com/creator" }),
),
response(
2,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(
3,
serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(4, serde_json::json!({ "typed": true })),
response(5, serde_json::json!({ "typed": true })),
response(6, serde_json::json!({ "scrolled": true })),
response(
7,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(8, serde_json::json!({ "ready": true })),
response(
9,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/p/123456/edit" }),
),
response(
10,
serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/p/123456/edit" }),
),
response(
11,
serde_json::json!({ "text": "自动发文能力测试", "url": "https://zhuanlan.zhihu.com/p/123456/edit" }),
),
]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
let result = execute(
transport.as_ref(),
&browser_tool,
ZhihuWriteRequest {
title: "自动发文能力测试".to_string(),
body: "第一段\n\n第二段".to_string(),
publish: true,
},
)
.unwrap();
assert_eq!(
result.final_url.as_deref(),
Some("https://zhuanlan.zhihu.com/p/123456")
);
assert_eq!(
result.summary,
"知乎文章已发布:自动发文能力测试 (https://zhuanlan.zhihu.com/p/123456)"
);
}
#[test]
fn zhihu_skill_fails_when_publish_confirmation_never_returns_article_url() {
let transport = Arc::new(MockTransport::new(vec![
response(
1,
serde_json::json!({ "url": "https://www.zhihu.com/creator" }),
),
response(
2,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(
3,
serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(4, serde_json::json!({ "typed": true })),
response(5, serde_json::json!({ "typed": true })),
response(6, serde_json::json!({ "scrolled": true })),
response(
7,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(8, serde_json::json!({ "ready": true })),
response(9, serde_json::json!({ "clicked": true })),
response(10, serde_json::json!({ "ready": true })),
response(11, serde_json::json!({ "text": "自动发文能力测试" })),
]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
let err = execute(
transport.as_ref(),
&browser_tool,
ZhihuWriteRequest {
title: "自动发文能力测试".to_string(),
body: "第一段\n\n第二段".to_string(),
publish: true,
},
)
.unwrap_err();
assert!(err.to_string().contains("did not return article url"));
}
#[test]
fn zhihu_skill_fails_when_published_title_does_not_match_request_title() {
let transport = Arc::new(MockTransport::new(vec![
response(
1,
serde_json::json!({ "url": "https://www.zhihu.com/creator" }),
),
response(
2,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(
3,
serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(4, serde_json::json!({ "typed": true })),
response(5, serde_json::json!({ "typed": true })),
response(6, serde_json::json!({ "scrolled": true })),
response(
7,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }),
),
response(8, serde_json::json!({ "ready": true })),
response(
9,
serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/p/123456" }),
),
response(
10,
serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/p/123456" }),
),
response(
11,
serde_json::json!({ "text": "别的标题", "url": "https://zhuanlan.zhihu.com/p/123456" }),
),
]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
let err = execute(
transport.as_ref(),
&browser_tool,
ZhihuWriteRequest {
title: "自动发文能力测试".to_string(),
body: "第一段\n\n第二段".to_string(),
publish: true,
},
)
.unwrap_err();
assert!(err
.to_string()
.contains("expected text `自动发文能力测试`, got `别的标题`"));
}