chore: auto release commit (2026-09-15 01:27:04)
Some checks failed
技能自动化发布 / release (push) Failing after 4s
Some checks failed
技能自动化发布 / release (push) Failing after 4s
This commit is contained in:
@@ -2,6 +2,15 @@
|
||||
|
||||
本文件对应技能市场 **「更新日志」** Tab。
|
||||
|
||||
## 1.1.27
|
||||
|
||||
- **修复「到 SPA 二级登录页卡 30 秒」问题**:
|
||||
- 之前步骤 4 等 URL 稳定到 `/app/` 或 `/select` 30 秒,但 sandbox 登录后会把人踢回 `/industries/campus/smartcampus/login`(URL 含 `/login`),等满 30 秒才发现要进 SPA 登录兜底
|
||||
- 步骤 4 改成等 URL 跳到 `/app/` 或 `/select` 或 `/login` 或 `/signin`(任一即可,**最多 30 秒**),URL 含 `/login` 立即退出等待
|
||||
- **新增 `_fill_demo_on_spa_login(page)`**:SPA 二级登录页 input 默认不预填(与根域不同),主动 `fill('demo')` + `fill('123456')` + 点登录按钮(Sign in / 登录 / Login / submit 任一)
|
||||
- 步骤 4.5 改为:URL 落在 SPA 登录页 → 自动 `_fill_demo_on_spa_login` → 不再卡 180 秒等用户输入
|
||||
- 新增 3 个测试覆盖 `_fill_demo_on_spa_login`(basic / 中文按钮 / 无 input),合计 66/66
|
||||
|
||||
## 1.1.26
|
||||
|
||||
- **按用户最新要求改回「自动点登录」行为**:「到了 /industries/campus/smartcampus/login 整个页面,有账号和密码可用直接点登录跳转」
|
||||
|
||||
2
SKILL.md
2
SKILL.md
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: 合同到期智能预警与续聘辅助
|
||||
description: "当用户说要扫描即将到期的合同、查 90/60/30 天预警名单、给某位教师生成续聘任务书、下发单位版续聘清单、从 SmartCampus HR 平台抓合同数据、解析续聘材料 PDF/Word、查看本月合同到期人员清单时,加载本技能。支持定时扫描(每日凌晨)+ 数据管理 + Agent 三入口,覆盖合同到期智能预警与续聘辅助全流程。"
|
||||
version: 1.1.26
|
||||
version: 1.1.27
|
||||
author: 深圳匠厂科技有限公司
|
||||
metadata:
|
||||
openclaw:
|
||||
|
||||
@@ -529,6 +529,76 @@ def _fill_root_login(page) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _fill_demo_on_spa_login(page) -> bool:
|
||||
"""v1.1.27:SPA 二级登录页(/industries/campus/smartcampus/login)
|
||||
input 默认**不预填**(与根域不同),必须主动 fill demo/123456 再点登录。
|
||||
|
||||
selector 优先级:
|
||||
- user: `#jc-user` → `input[type='tel']` → `input[autocomplete='username']`
|
||||
- pass: `#jc-pass` → `input[type='password']` → `input[autocomplete='current-password']`
|
||||
按钮文本优先级:Sign in → 登录 → Login → signin → submit[type]。
|
||||
|
||||
返回是否成功点登录按钮。
|
||||
"""
|
||||
# 找 user input
|
||||
user_loc = None
|
||||
for sel in ("#jc-user", "input[type='tel']", "input[autocomplete='username']"):
|
||||
loc = page.locator(sel).first
|
||||
if loc.count() > 0:
|
||||
user_loc = loc
|
||||
break
|
||||
if user_loc is None:
|
||||
logger.warning(
|
||||
"platform_collector._fill_demo_on_spa_login: 找不到 user input "
|
||||
"(#jc-user / tel / autocomplete=username 全 miss)"
|
||||
)
|
||||
return False
|
||||
|
||||
# 找 pass input
|
||||
pass_loc = None
|
||||
for sel in ("#jc-pass", "input[type='password']", "input[autocomplete='current-password']"):
|
||||
loc = page.locator(sel).first
|
||||
if loc.count() > 0:
|
||||
pass_loc = loc
|
||||
break
|
||||
if pass_loc is None:
|
||||
logger.warning(
|
||||
"platform_collector._fill_demo_on_spa_login: 找不到 pass input"
|
||||
)
|
||||
return False
|
||||
|
||||
user_loc.fill("demo")
|
||||
pass_loc.fill("123456")
|
||||
logger.info(
|
||||
"platform_collector: SPA 二级登录页 fill demo/123456 完成"
|
||||
)
|
||||
|
||||
# 找登录按钮
|
||||
btn = None
|
||||
btn_text_used = ""
|
||||
for txt in ("Sign in", "登录", "Login", "signin"):
|
||||
loc = page.locator(f"button:has-text('{txt}')").first
|
||||
if loc.count() > 0:
|
||||
btn = loc
|
||||
btn_text_used = txt
|
||||
break
|
||||
if btn is None:
|
||||
btn = page.locator("button[type='submit']").first
|
||||
btn_text_used = "submit"
|
||||
if btn is None or btn.count() == 0:
|
||||
logger.warning(
|
||||
"platform_collector._fill_demo_on_spa_login: 找不到登录按钮"
|
||||
)
|
||||
return False
|
||||
|
||||
logger.info(
|
||||
"platform_collector: SPA 二级登录页点 %s 按钮(demo/123456)",
|
||||
btn_text_used,
|
||||
)
|
||||
btn.click()
|
||||
return True
|
||||
|
||||
|
||||
def _click_campus_link(page) -> bool:
|
||||
"""在 /select 页面点智慧校园 tile,返回是否点中。"""
|
||||
link = page.locator("a[href*='/industries/campus']").first
|
||||
@@ -743,44 +813,52 @@ def collect_all(
|
||||
f"(请用 --show-browser 重试)"
|
||||
)
|
||||
|
||||
# 4. 登录完成 → 等 URL 真正稳定到 /app/ 或 /select(最多 30s,**不做任何操作**)
|
||||
# v1.1.22:sandbox 登录后会先跳 /select,再自动跳 /app/home,
|
||||
# 所以两个 URL 都算"SPA 已路由"
|
||||
if not _wait_for_url_stable(page, ("/app/", "/select"), max_wait_sec=30):
|
||||
# 4. v1.1.27:等 URL 跳到任一「SPA 已路由」状态(最多 30s,**不做任何操作**)
|
||||
# - /app/ = 已到 SPA 主页
|
||||
# - /select = sandbox 中间页
|
||||
# - /login 或 /signin = sandbox 又把人踢回登录页(根域登录后常见,
|
||||
# sandbox 会跳到 /industries/campus/smartcampus/login SPA 二级登录页)
|
||||
# - 如果 URL 跳到 login/signin,**立即**退出 30 秒等待(不再干等),
|
||||
# 让 4.5 处理。否则会等满 30 秒才发现要进 SPA 登录兜底,造成卡顿。
|
||||
spa_url_seen = False
|
||||
if not _wait_for_url_stable(
|
||||
page, ("/app/", "/select", "/login", "/signin"), max_wait_sec=30,
|
||||
):
|
||||
logger.warning(
|
||||
"platform_collector: 登录后 30s 内 URL 未稳定到 /app/或 /select(当前 URL=%s)",
|
||||
"platform_collector: 登录后 30s 内 URL 未稳定到 /app/、/select、"
|
||||
"/login 或 /signin(当前 URL=%s)",
|
||||
page.url,
|
||||
)
|
||||
# 不强行 reload —— 让用户决定是否手工刷新
|
||||
|
||||
# 4.5. v1.1.24:sandbox 可能登录后把人踢回 SPA 二级登录页
|
||||
# (/industries/campus/smartcampus/login)。如果 URL 落在
|
||||
# 这个二级登录页,再走一次 _try_auto_sign_in(已经扩展支持
|
||||
# 中文「登录」按钮);若 SPA 登录页 input 有预填账号 → 自动点;
|
||||
# 若 input 空 → 等用户输入并**自己点登录**(脚本不抢按钮)。
|
||||
# 4.5. v1.1.24/27:URL 落在 SPA 二级登录页 → 自动填 demo/123456 再登录
|
||||
# sandbox SPA 登录页 input 默认**不预填**(与根域不同),
|
||||
# 所以 _try_auto_sign_in 不会自动点 —— 必须显式 fill demo/123456。
|
||||
cur_url_after_stable = page.url.lower()
|
||||
if "/login" in cur_url_after_stable or "/signin" in cur_url_after_stable:
|
||||
logger.warning(
|
||||
"platform_collector: 登录后仍落在登录页(%s),"
|
||||
"v1.1.24 触发 SPA 二级登录兜底",
|
||||
"platform_collector: 登录后落在 SPA 二级登录页(%s),"
|
||||
"v1.1.27 触发 demo/123456 自动 fill",
|
||||
page.url,
|
||||
)
|
||||
spa_auto = _try_auto_sign_in(page, wait_input_sec=2)
|
||||
if spa_auto:
|
||||
spa_filled = _fill_demo_on_spa_login(page)
|
||||
if spa_filled:
|
||||
logger.info(
|
||||
"platform_collector: ✅ SPA 二级登录页已自动登录,"
|
||||
"等 URL 跳到 /app/ 或 /select"
|
||||
"platform_collector: ✅ SPA 二级登录页 demo/123456 自动填好,"
|
||||
"已点登录,等 URL 跳到 /app/ 或 /select"
|
||||
)
|
||||
_wait_for_url_stable(page, ("/app/", "/select"), max_wait_sec=30)
|
||||
elif not headless:
|
||||
if _wait_for_login_inputs_filled(page, wait_sec=human_wait_timeout):
|
||||
_wait_for_url_stable(page, ("/app/", "/select"), max_wait_sec=30)
|
||||
else:
|
||||
_wait_for_manual_login(page, wait_sec=human_wait_timeout)
|
||||
else:
|
||||
raise LoginTimeoutError(
|
||||
f"SPA 二级登录页 input 空且 headless 无法手工登录(URL={page.url})"
|
||||
)
|
||||
# demo fill 失败(input 不存在)→ 等用户手工输入
|
||||
if not headless:
|
||||
if _wait_for_login_inputs_filled(page, wait_sec=human_wait_timeout):
|
||||
_wait_for_url_stable(page, ("/app/", "/select"), max_wait_sec=30)
|
||||
else:
|
||||
_wait_for_manual_login(page, wait_sec=human_wait_timeout)
|
||||
else:
|
||||
raise LoginTimeoutError(
|
||||
f"SPA 二级登录页 demo fill 失败且 headless 无法手工登录"
|
||||
f"(URL={page.url})"
|
||||
)
|
||||
|
||||
# 5. v1.1.22:显式跳到 SPA_HOME_URL(/app/home),等侧边栏 link 出现
|
||||
# 避免 sandbox 在 /select 中间页时直接找 sidebar link 找不到
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"""
|
||||
|
||||
SKILL_SLUG = "alert-contract-expiring-hr"
|
||||
SKILL_VERSION = "1.1.26"
|
||||
SKILL_VERSION = "1.1.27"
|
||||
LOG_LOGGER_NAME = "openclaw.skill.alert_contract_expiring_hr"
|
||||
PLATFORM_KIT_MIN_VERSION = "1.2.11"
|
||||
|
||||
|
||||
@@ -1408,6 +1408,10 @@ class _FakeLocator:
|
||||
def click(self) -> None:
|
||||
self._click_log.append(self._value or "clicked")
|
||||
|
||||
def fill(self, value: str) -> None:
|
||||
"""v1.1.27:fill 后 value 立即更新。"""
|
||||
self._value = value
|
||||
|
||||
|
||||
class _FakeLocatorRouter:
|
||||
"""根据 selector 返回对应 _FakeLocator 的路由器。
|
||||
@@ -1588,6 +1592,61 @@ def test_wait_for_login_inputs_filled_auto_click_v1_26() -> tuple[bool, str]:
|
||||
pc.HUMAN_PROMPT_INTERVAL = orig_prompt
|
||||
|
||||
|
||||
def test_fill_demo_on_spa_login_basic_v1_27() -> tuple[bool, str]:
|
||||
"""v1.1.27:`_fill_demo_on_spa_login` ——SPA 二级登录页 input 默认不预填,
|
||||
必须主动 fill demo/123456 再点登录按钮。
|
||||
"""
|
||||
click_log: list[str] = []
|
||||
page = _FakePlaywrightPage(
|
||||
url="https://sandbox.jc2009.com/industries/campus/smartcampus/login",
|
||||
selectors={
|
||||
"#jc-user": _FakeLocator(count=1, value=""), # 空 → 要 fill
|
||||
"#jc-pass": _FakeLocator(count=1, value=""),
|
||||
"button:has-text('Sign in')": _FakeLocator(count=1, click_log=click_log),
|
||||
},
|
||||
)
|
||||
from service.platform_collector import _fill_demo_on_spa_login
|
||||
|
||||
if not _fill_demo_on_spa_login(page):
|
||||
return False, "SPA login fill demo 应成功"
|
||||
if not click_log:
|
||||
return False, f"应点 Sign in,但 click_log={click_log}"
|
||||
return True, f"SPA 二级登录页 demo/123456 自动 fill+点 OK(click_log={click_log})"
|
||||
|
||||
|
||||
def test_fill_demo_on_spa_login_chinese_button_v1_27() -> tuple[bool, str]:
|
||||
"""v1.1.27:中文「登录」按钮(用户截图版本)也能 fill demo/123456 并点。"""
|
||||
click_log: list[str] = []
|
||||
page = _FakePlaywrightPage(
|
||||
url="https://sandbox.jc2009.com/industries/campus/smartcampus/login",
|
||||
selectors={
|
||||
"input[type='tel']": _FakeLocator(count=1, value=""),
|
||||
"input[type='password']": _FakeLocator(count=1, value=""),
|
||||
"button:has-text('登录')": _FakeLocator(count=1, click_log=click_log),
|
||||
},
|
||||
)
|
||||
from service.platform_collector import _fill_demo_on_spa_login
|
||||
|
||||
if not _fill_demo_on_spa_login(page):
|
||||
return False, "SPA login 中文按钮 fill demo 应成功"
|
||||
if not click_log:
|
||||
return False, f"应点 登录,但 click_log={click_log}"
|
||||
return True, f"SPA 二级登录页中文「登录」按钮 OK(click_log={click_log})"
|
||||
|
||||
|
||||
def test_fill_demo_on_spa_login_no_input_returns_false_v1_27() -> tuple[bool, str]:
|
||||
"""v1.1.27:找不到 user input(页面不是 login 页)→ 返回 False。"""
|
||||
page = _FakePlaywrightPage(
|
||||
url="https://sandbox.jc2009.com/industries/campus/smartcampus/app/home",
|
||||
selectors={},
|
||||
)
|
||||
from service.platform_collector import _fill_demo_on_spa_login
|
||||
|
||||
if _fill_demo_on_spa_login(page):
|
||||
return False, "无 login input 应返回 False"
|
||||
return True, "无 login input 返回 False OK"
|
||||
|
||||
|
||||
# ---------- v1.1.18: show-config / logs-list ----------
|
||||
|
||||
|
||||
@@ -1866,6 +1925,9 @@ def main() -> int:
|
||||
_run("login_flow/auto_sign_in_no_input_returns_false", test_try_auto_sign_in_no_input_returns_false)
|
||||
_run("login_flow/auto_sign_in_selector_timeout_returns_false", test_try_auto_sign_in_selector_timeout_returns_false)
|
||||
_run("login_flow/wait_login_inputs_filled_auto_click_v1_26", test_wait_for_login_inputs_filled_auto_click_v1_26)
|
||||
_run("login_flow/fill_demo_on_spa_login_basic_v1_27", test_fill_demo_on_spa_login_basic_v1_27)
|
||||
_run("login_flow/fill_demo_on_spa_login_chinese_button_v1_27", test_fill_demo_on_spa_login_chinese_button_v1_27)
|
||||
_run("login_flow/fill_demo_on_spa_login_no_input_returns_false_v1_27", test_fill_demo_on_spa_login_no_input_returns_false_v1_27)
|
||||
_run("show_config/masks_tokens", test_show_config_masks_tokens)
|
||||
_run("show_config/skips_empty", test_show_config_skips_empty)
|
||||
_run("show_config/skips_comments", test_show_config_skips_comments)
|
||||
|
||||
Reference in New Issue
Block a user