Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion doc/agent-smoke-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ bash scripts/agent-smoke.sh --cdp-port 9222 --cases AGENT-CHROME-001,AGENT-CHROM
1. `上一帖` 按钮存在
2. `下一帖` 按钮存在
3. `选项` 按钮存在
4. `新标签打开` 链接存在
4. `回复主题` 按钮存在
5. `新标签打开` 链接存在

### AGENT-CHROME-003:设置面板打开和关闭

Expand Down Expand Up @@ -266,6 +267,49 @@ bash scripts/agent-smoke.sh --cdp-port 9222 --cases AGENT-CHROME-001,AGENT-CHROM
2. 抽屉保持打开
3. `location.href` 仍然停留在列表页

### AGENT-CHROME-014:主题回复入口与悬浮开关

范围:
头部 `回复主题` 主入口 + 可选悬浮快捷入口

状态:
当前不在默认批量脚本中

步骤:

1. 打开任一主题抽屉
2. 确认头部工具栏存在 `回复主题`
3. 读取右侧 `.ld-drawer-reply-fab` 的可见性
4. 在 `选项` 中把 `悬浮回复入口` 切到 `开启`
5. 点击头部 `回复主题` 或右侧悬浮按钮

断言:

1. 默认仅显示头部 `回复主题`,右侧悬浮按钮默认隐藏
2. 切到 `开启` 后,右侧悬浮按钮变为可见
3. 设置层打开时,右侧悬浮按钮不应继续透出或可点击
4. 点击任一主题级回复入口后,`#ld-drawer-reply-panel` 打开
5. 回复面板标题默认是 `回复主题`

### AGENT-CHROME-015:头部工具栏按钮不应挤成竖排

范围:
抽屉头部工具栏布局回归

状态:
当前不在默认批量脚本中

步骤:

1. 打开任一主题抽屉
2. 读取 `.ld-drawer-actions` 中可见按钮和链接的布局信息

断言:

1. 头部工具栏可见操作项不少于 5 个
2. 文本按钮和链接都保持 `white-space: nowrap`
3. 可见文本按钮的宽度应明显大于高度,避免被挤成竖排窄条

## 3. 记录格式

执行完用例后,建议按下面格式留证据:
Expand Down
258 changes: 256 additions & 2 deletions scripts/agent-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ DEFAULT_CASES=(
"AGENT-CHROME-010"
)

ALL_CASES=(
"${DEFAULT_CASES[@]}"
"AGENT-CHROME-013"
"AGENT-CHROME-014"
"AGENT-CHROME-015"
)

CASES=("${DEFAULT_CASES[@]}")
PASS_COUNT=0
FAIL_COUNT=0
Expand All @@ -34,7 +41,7 @@ EOF
}

list_cases() {
printf '%s\n' "${DEFAULT_CASES[@]}"
printf '%s\n' "${ALL_CASES[@]}"
}

while [ "$#" -gt 0 ]; do
Expand Down Expand Up @@ -137,6 +144,7 @@ drawer_state() {
const root = document.getElementById("ld-drawer-root");
const settingsButton = root?.querySelector(".ld-drawer-settings-toggle");
const settingsPanel = root?.querySelector("#ld-drawer-settings");
const replyButton = root?.querySelector(".ld-drawer-reply-toggle");
const link = root?.querySelector(".ld-drawer-link");
return {
pageUrl: location.href,
Expand All @@ -148,6 +156,8 @@ drawer_state() {
settingsText: settingsButton?.textContent?.trim() || null,
settingsExpanded: settingsButton?.getAttribute("aria-expanded") || null,
settingsHidden: settingsPanel?.hasAttribute("hidden") ?? null,
replyText: replyButton?.textContent?.trim() || null,
replyHidden: replyButton?.hidden ?? null,
newTabText: link?.textContent?.trim() || null,
newTabHref: link?.getAttribute("href") || null,
newTabTarget: link?.getAttribute("target") || null,
Expand Down Expand Up @@ -543,6 +553,139 @@ EOF
ab_eval "$script"
}

reply_entry_state() {
local script
script=$(cat <<'EOF'
(() => {
const root = document.getElementById("ld-drawer-root");
const headerButton = root?.querySelector(".ld-drawer-reply-toggle");
const fabButton = root?.querySelector(".ld-drawer-reply-fab");
const panel = root?.querySelector("#ld-drawer-reply-panel");
const fabRect = fabButton?.getBoundingClientRect?.() || null;
const fabStyle = fabButton ? getComputedStyle(fabButton) : null;
return {
headerExists: headerButton instanceof HTMLButtonElement,
headerText: headerButton?.textContent?.trim() || null,
headerHidden: headerButton?.hidden ?? null,
headerExpanded: headerButton?.getAttribute("aria-expanded") || null,
fabExists: fabButton instanceof HTMLButtonElement,
fabText: fabButton?.textContent?.trim() || null,
fabHidden: fabButton?.hidden ?? null,
fabDisplay: fabStyle?.display || null,
fabRendered: Boolean(fabRect && fabRect.width > 0 && fabRect.height > 0),
fabExpanded: fabButton?.getAttribute("aria-expanded") || null,
panelHidden: panel?.hasAttribute("hidden") ?? null,
panelTitle: panel?.querySelector(".ld-reply-panel-title")?.textContent?.trim() || null
};
})()
EOF
)
ab_eval "$script"
}

toolbar_layout_state() {
local script
script=$(cat <<'EOF'
(() => {
const toolbar = document.querySelector("#ld-drawer-root .ld-drawer-toolbar");
const actions = document.querySelector("#ld-drawer-root .ld-drawer-actions");
const items = Array.from(actions?.querySelectorAll("button, a") || [])
.filter((node) => !node.hidden)
.map((node) => {
const rect = node.getBoundingClientRect();
const style = getComputedStyle(node);
const text = (node.textContent || "").replace(/\s+/g, " ").trim();
return {
text,
width: Math.round(rect.width),
height: Math.round(rect.height),
whiteSpace: style.whiteSpace,
display: style.display
};
});

const toolbarStyle = toolbar ? getComputedStyle(toolbar) : null;
const visibleTextItems = items.filter((item) => item.text);

return {
toolbarExists: Boolean(toolbar),
actionsExists: Boolean(actions),
gridTemplateColumns: toolbarStyle?.gridTemplateColumns || null,
visibleCount: items.length,
items,
nowrapOk: visibleTextItems.every((item) => item.whiteSpace === "nowrap"),
widthOk: visibleTextItems.every((item) => item.width >= 44 && item.width > item.height)
};
})()
EOF
)
ab_eval "$script"
}

click_reply_trigger() {
local target=$1
local selector_json script
case "$target" in
header)
selector_json='"#ld-drawer-root .ld-drawer-reply-toggle"'
;;
fab)
selector_json='"#ld-drawer-root .ld-drawer-reply-fab"'
;;
*)
echo "{\"ok\":false,\"reason\":\"unknown-target\",\"target\":\"$target\"}"
return 0
;;
esac

script=$(cat <<EOF
(() => {
const target = $(printf '%s' "$target" | jq -Rs .);
const button = document.querySelector($selector_json);
if (!(button instanceof HTMLButtonElement)) {
return { ok: false, reason: "reply-trigger-not-found", target };
}

button.click();
return {
ok: true,
target,
hidden: button.hidden,
expanded: button.getAttribute("aria-expanded") || null,
text: button.textContent?.trim() || null
};
})()
EOF
)
ab_eval "$script"
}

close_reply_panel_if_open() {
local script
script=$(cat <<'EOF'
(() => {
const panel = document.querySelector("#ld-drawer-root #ld-drawer-reply-panel");
if (!(panel instanceof HTMLElement)) {
return { ok: false, reason: "reply-panel-not-found" };
}

if (panel.hasAttribute("hidden")) {
return { ok: true, closed: false };
}

const button = panel.querySelector(".ld-reply-panel-close");
if (!(button instanceof HTMLButtonElement)) {
return { ok: false, reason: "reply-panel-close-not-found" };
}

button.click();
return { ok: true, closed: true };
})()
EOF
)
ab_eval "$script"
}

click_refresh_button() {
local script
script=$(cat <<'EOF'
Expand Down Expand Up @@ -601,6 +744,8 @@ run_002() {
if [ "$(echo "$state" | jq -r '.prevText // empty')" = "上一帖" ] \
&& [ "$(echo "$state" | jq -r '.nextText // empty')" = "下一帖" ] \
&& [ "$(echo "$state" | jq -r '.settingsText // empty')" = "选项" ] \
&& [ "$(echo "$state" | jq -r '.replyText // empty')" = "回复主题" ] \
&& [ "$(echo "$state" | jq -r '.replyHidden')" = "false" ] \
&& [ "$(echo "$state" | jq -r '.newTabText // empty')" = "新标签打开" ]; then
record_pass "$case_id" "toolbar ready"
else
Expand Down Expand Up @@ -802,7 +947,12 @@ run_010() {
return 0
}

before=$(refresh_button_state)
if ! before=$(wait_for_refresh_idle); then
restore_settings_subset "$original_settings"
record_fail "$case_id" "刷新按钮在切换场景后未进入可点击状态: $(echo "$before" | jq -c '{exists, hidden, disabled, text}')"
return 0
fi

click=$(click_refresh_button)
if ! after=$(wait_for_refresh_idle); then
:
Expand Down Expand Up @@ -978,6 +1128,108 @@ run_013() {
fi
}

run_014() {
local case_id="AGENT-CHROME-014"
local original_settings original_floating off_result off_state on_result on_state settings_open_state click_result panel_state
local passed=0
local detail=""

open_topic_by_index 0 >/dev/null || {
record_fail "$case_id" "无法打开抽屉"
return 0
}

original_settings=$(settings_values)
original_floating=$(echo "$original_settings" | jq -r '.floatingReplyButton // empty')
if [ -z "$original_floating" ]; then
record_fail "$case_id" "无法读取悬浮回复入口设置"
return 0
fi

off_result=$(set_setting_value floatingReplyButton off)
if [ "$(echo "$off_result" | jq -r '.ok')" != "true" ]; then
record_fail "$case_id" "无法关闭悬浮回复入口: $off_result"
return 0
fi
ab wait 1200 >/dev/null
off_state=$(reply_entry_state)

on_result=$(set_setting_value floatingReplyButton on)
if [ "$(echo "$on_result" | jq -r '.ok')" != "true" ]; then
set_setting_value floatingReplyButton "$original_floating" >/dev/null || true
record_fail "$case_id" "无法开启悬浮回复入口: $on_result"
return 0
fi
ab wait 1200 >/dev/null
on_state=$(reply_entry_state)

ensure_settings_open
settings_open_state=$(reply_entry_state)
ensure_settings_closed
ab wait 400 >/dev/null

click_result=$(click_reply_trigger fab)
ab wait 800 >/dev/null
panel_state=$(reply_entry_state)

close_reply_panel_if_open >/dev/null || true
ab wait 400 >/dev/null
set_setting_value floatingReplyButton "$original_floating" >/dev/null || true
ab wait 800 >/dev/null
ensure_settings_closed

if [ "$(echo "$off_state" | jq -r '.headerExists')" = "true" ] \
&& [ "$(echo "$off_state" | jq -r '.headerText // empty')" = "回复主题" ] \
&& [ "$(echo "$off_state" | jq -r '.headerHidden')" = "false" ] \
&& [ "$(echo "$off_state" | jq -r '.fabExists')" = "true" ] \
&& [ "$(echo "$off_state" | jq -r '.fabHidden')" = "true" ] \
&& [ "$(echo "$off_state" | jq -r '.fabDisplay // empty')" = "none" ] \
&& [ "$(echo "$off_state" | jq -r '.fabRendered')" = "false" ] \
&& [ "$(echo "$on_state" | jq -r '.fabHidden')" = "false" ] \
&& [ "$(echo "$on_state" | jq -r '.fabDisplay // empty')" != "none" ] \
&& [ "$(echo "$on_state" | jq -r '.fabRendered')" = "true" ] \
&& [ "$(echo "$settings_open_state" | jq -r '.fabHidden')" = "true" ] \
&& [ "$(echo "$settings_open_state" | jq -r '.fabDisplay // empty')" = "none" ] \
&& [ "$(echo "$settings_open_state" | jq -r '.fabRendered')" = "false" ] \
&& [ "$(echo "$click_result" | jq -r '.ok')" = "true" ] \
&& [ "$(echo "$click_result" | jq -r '.target // empty')" = "fab" ] \
&& [ "$(echo "$panel_state" | jq -r '.panelHidden')" = "false" ] \
&& [ "$(echo "$panel_state" | jq -r '.panelTitle // empty')" = "回复主题" ]; then
passed=1
detail="off=$(echo "$off_state" | jq -c '{headerHidden, fabHidden, fabDisplay, fabRendered}') on=$(echo "$on_state" | jq -c '{fabHidden, fabDisplay, fabRendered}') settings=$(echo "$settings_open_state" | jq -c '{fabHidden, fabDisplay, fabRendered}') panel=$(echo "$panel_state" | jq -c '{panelHidden, panelTitle}')"
else
detail="off=$(echo "$off_state" | jq -c '{headerExists, headerText, headerHidden, fabExists, fabHidden, fabDisplay, fabRendered}') on=$(echo "$on_state" | jq -c '{fabHidden, fabDisplay, fabRendered}') settings=$(echo "$settings_open_state" | jq -c '{fabHidden, fabDisplay, fabRendered, panelHidden}') click=$(echo "$click_result" | jq -c '.') panel=$(echo "$panel_state" | jq -c '{panelHidden, panelTitle, headerExpanded, fabExpanded}')"
fi

if [ "$passed" -eq 1 ]; then
record_pass "$case_id" "$detail"
else
record_fail "$case_id" "$detail"
fi
}

run_015() {
local case_id="AGENT-CHROME-015"
local state

open_topic_by_index 0 >/dev/null || {
record_fail "$case_id" "无法打开抽屉"
return 0
}

state=$(toolbar_layout_state)

if [ "$(echo "$state" | jq -r '.toolbarExists')" = "true" ] \
&& [ "$(echo "$state" | jq -r '.actionsExists')" = "true" ] \
&& [ "$(echo "$state" | jq -r '.nowrapOk')" = "true" ] \
&& [ "$(echo "$state" | jq -r '.widthOk')" = "true" ] \
&& [ "$(echo "$state" | jq -r '.visibleCount')" -ge 5 ]; then
record_pass "$case_id" "items=$(echo "$state" | jq -c '.items | map({text, width, height, whiteSpace})')"
else
record_fail "$case_id" "state=$(echo "$state" | jq -c '{gridTemplateColumns, visibleCount, nowrapOk, widthOk, items}')"
fi
}

run_case() {
local case_id=$1
case "$case_id" in
Expand All @@ -991,6 +1243,8 @@ run_case() {
AGENT-CHROME-009) run_009 ;;
AGENT-CHROME-010) run_010 ;;
AGENT-CHROME-013) run_013 ;;
AGENT-CHROME-014) run_014 ;;
AGENT-CHROME-015) run_015 ;;
*)
record_fail "$case_id" "未知用例 ID"
;;
Expand Down
Loading
Loading