147 lines
4.6 KiB
Python
147 lines
4.6 KiB
Python
import json
|
|
import subprocess
|
|
import textwrap
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
SCRIPT_PATH = (
|
|
REPO_ROOT.parent / "skill_lib" / "skills" / "zhihu-navigate" / "scripts" /
|
|
"open_creator_entry.js"
|
|
)
|
|
|
|
|
|
def run_open_creator_entry(*, body_text: str, selectors: dict[str, list[dict]]) -> dict:
|
|
node_script = textwrap.dedent(
|
|
f"""
|
|
import fs from 'node:fs';
|
|
import vm from 'node:vm';
|
|
|
|
const scriptPath = {json.dumps(str(SCRIPT_PATH))};
|
|
const selectorMap = {json.dumps(selectors, ensure_ascii=False)};
|
|
const bodyText = {json.dumps(body_text, ensure_ascii=False)};
|
|
const source = fs.readFileSync(scriptPath, 'utf8');
|
|
|
|
function createNode(spec) {{
|
|
const node = {{
|
|
tagName: String(spec?.tagName || 'DIV').toUpperCase(),
|
|
textContent: String(spec?.textContent ?? ''),
|
|
innerText: String(spec?.innerText ?? spec?.textContent ?? ''),
|
|
href: String(spec?.href ?? ''),
|
|
clicked: false,
|
|
click() {{
|
|
this.clicked = true;
|
|
}},
|
|
getBoundingClientRect() {{
|
|
return {{
|
|
width: spec?.visible === false ? 0 : 120,
|
|
height: spec?.visible === false ? 0 : 32,
|
|
}};
|
|
}},
|
|
}};
|
|
return node;
|
|
}}
|
|
|
|
const created = new Map();
|
|
function createNodeList(selector) {{
|
|
const specs = selectorMap[selector] || [];
|
|
return specs.map((spec, index) => {{
|
|
const key = `${{selector}}#${{index}}`;
|
|
if (!created.has(key)) {{
|
|
created.set(key, createNode(spec));
|
|
}}
|
|
return created.get(key);
|
|
}});
|
|
}}
|
|
|
|
const bodyNode = createNode({{ tagName: 'BODY', textContent: bodyText, innerText: bodyText }});
|
|
const context = {{
|
|
args: {{ desired_target: 'article_editor' }},
|
|
location: {{ href: 'https://www.zhihu.com/creator' }},
|
|
document: {{
|
|
body: bodyNode,
|
|
querySelector(selector) {{
|
|
if (selector === 'body') {{
|
|
return bodyNode;
|
|
}}
|
|
return createNodeList(selector)[0] || null;
|
|
}},
|
|
querySelectorAll(selector) {{
|
|
return createNodeList(selector);
|
|
}},
|
|
}},
|
|
console,
|
|
JSON,
|
|
Math,
|
|
Number,
|
|
Object,
|
|
RegExp,
|
|
Set,
|
|
String,
|
|
Array,
|
|
Error,
|
|
}};
|
|
|
|
try {{
|
|
const result = vm.runInNewContext(`(function(){{\\n${{source}}\\n}})()`, context);
|
|
process.stdout.write(JSON.stringify({{ ok: true, result, created: Object.fromEntries(created) }}));
|
|
}} catch (error) {{
|
|
process.stdout.write(JSON.stringify({{
|
|
ok: false,
|
|
error: String(error && error.message ? error.message : error),
|
|
}}));
|
|
process.exitCode = 1;
|
|
}}
|
|
"""
|
|
)
|
|
completed = subprocess.run(
|
|
["node", "--input-type=module", "-e", node_script],
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
payload = json.loads(completed.stdout)
|
|
if completed.returncode != 0:
|
|
raise AssertionError(payload["error"])
|
|
return payload
|
|
|
|
|
|
class SkillScriptZhihuNavigateTest(unittest.TestCase):
|
|
def test_open_creator_entry_clicks_anchor_write_entry(self):
|
|
payload = run_open_creator_entry(
|
|
body_text="创作者中心 写文章",
|
|
selectors={
|
|
"a[href], button, [role='button']": [
|
|
{
|
|
"tagName": "a",
|
|
"textContent": "写文章",
|
|
"href": "https://zhuanlan.zhihu.com/write",
|
|
}
|
|
]
|
|
},
|
|
)
|
|
|
|
self.assertEqual(payload["result"]["status"], "creator_entry_clicked")
|
|
self.assertTrue(payload["created"]["a[href], button, [role='button']#0"]["clicked"])
|
|
|
|
def test_open_creator_entry_clicks_button_write_entry(self):
|
|
payload = run_open_creator_entry(
|
|
body_text="创作者中心 发布内容",
|
|
selectors={
|
|
"a[href], button, [role='button']": [
|
|
{
|
|
"tagName": "button",
|
|
"textContent": "写文章",
|
|
}
|
|
]
|
|
},
|
|
)
|
|
|
|
self.assertEqual(payload["result"]["status"], "creator_entry_clicked")
|
|
self.assertTrue(payload["created"]["a[href], button, [role='button']#0"]["clicked"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|