fix(web-probe): 对齐新版 MDTODO DOM 合同

This commit is contained in:
Codex
2026-07-13 14:23:05 +02:00
parent c5e8f163b5
commit cd7fb392f6
4 changed files with 114 additions and 28 deletions
@@ -764,7 +764,7 @@ async function selectMdtodoTask(command) {
const clicked = await target.locator.evaluate((element) => ({
taskRef: element.getAttribute("data-task-ref") || null,
taskId: element.getAttribute("data-task-id") || element.getAttribute("data-rxx-id") || null,
status: element.getAttribute("data-task-status") || null,
status: element.getAttribute("data-task-status") || element.querySelector("[data-state]")?.getAttribute("data-state") || null,
selected: element.getAttribute("data-selected") === "true" || element.getAttribute("aria-selected") === "true"
})).catch(() => ({ taskRef: target.taskRef || null, taskId: target.taskId || null, status: null }));
if (!clicked.selected) {
@@ -895,12 +895,22 @@ async function closeMdtodoSourceConfigIfOpen(options = {}) {
return result;
}
function mdtodoTestIdCandidates(value) {
return (Array.isArray(value) ? value : [value]).filter((item) => typeof item === "string" && item.length > 0);
}
function mdtodoTestIdLocator(value) {
const candidates = mdtodoTestIdCandidates(value);
return page.locator(candidates.map((testId) => '[data-testid="' + cssEscape(testId) + '"]').join(", ")).first();
}
async function fillMdtodoField(testId, value) {
if (typeof value !== "string" || !value.trim()) return { testId, filled: false };
const locator = page.locator('[data-testid="' + cssEscape(testId) + '"]').first();
const locator = mdtodoTestIdLocator(testId);
await locator.waitFor({ state: "visible", timeout: 10000 });
await locator.fill(value);
return { testId, filled: true, value: opaqueIdSummary(value), valuesRedacted: true };
const resolvedTestId = await locator.getAttribute("data-testid").catch(() => null);
return { testId: resolvedTestId || mdtodoTestIdCandidates(testId)[0] || null, filled: true, value: opaqueIdSummary(value), valuesRedacted: true };
}
async function clickProjectButtonAndMaybeWait(testId, pathPattern) {
@@ -1003,7 +1013,7 @@ async function saveMdtodoTaskWithButton(command, type, testId, fields) {
if (field.openByDblClickTestId) inlineEditors.push(await ensureMdtodoInlineEditor(field.testId, field.openByDblClickTestId));
if (field.kind === "fill") await fillMdtodoField(field.testId, field.value);
if (field.kind === "select") {
const locator = page.locator('[data-testid="' + cssEscape(field.testId) + '"]').first();
const locator = mdtodoTestIdLocator(field.testId);
await locator.waitFor({ state: "visible", timeout: 10000 });
await selectHtmlOptionByValueOrLabel(locator, field.value);
}
@@ -1023,9 +1033,9 @@ async function clickProjectButtonAndMaybeWaitAny(testIds, pathPattern) {
}
async function ensureMdtodoInlineEditor(editorTestId, readTestId) {
const editor = page.locator('[data-testid="' + cssEscape(editorTestId) + '"]').first();
const editor = mdtodoTestIdLocator(editorTestId);
if (await visibleLocator(editor)) return { editorTestId, readTestId, opened: false };
const read = page.locator('[data-testid="' + cssEscape(readTestId) + '"]').first();
const read = mdtodoTestIdLocator(readTestId);
await read.waitFor({ state: "visible", timeout: 10000 });
await read.dblclick();
await editor.waitFor({ state: "visible", timeout: 10000 });
@@ -1041,7 +1051,7 @@ async function editMdtodoTaskTitle(command) {
async function editMdtodoTaskBody(command) {
const body = commandValue(command, ["text", "body", "value"]);
if (!body) throw new Error("editMdtodoTaskBody requires --text or --text-stdin");
return saveMdtodoTaskWithButton(command, "editMdtodoTaskBody", "mdtodo-edit-body-save", [{ kind: "fill", testId: "mdtodo-edit-body", value: body, openByDblClickTestId: "mdtodo-body-rendered" }]);
return saveMdtodoTaskWithButton(command, "editMdtodoTaskBody", ["mdtodo-body-save", "mdtodo-edit-body-save"], [{ kind: "fill", testId: ["mdtodo-body-editor", "mdtodo-edit-body"], value: body, openByDblClickTestId: ["mdtodo-body-read", "mdtodo-body-rendered"] }]);
}
async function toggleMdtodoTaskStatus(command) {
@@ -1060,7 +1070,7 @@ async function editMdtodoTaskInline(command) {
if (field === "body" || field === "content") {
const body = commandValue(command, ["body", "text"]);
if (!body) throw new Error("editMdtodoTaskInline --field body requires --body, --text, or --text-stdin");
return saveMdtodoTaskWithButton(command, "editMdtodoTaskInline", "mdtodo-edit-body-save", [{ kind: "fill", testId: "mdtodo-edit-body", value: body, openByDblClickTestId: "mdtodo-body-rendered" }]);
return saveMdtodoTaskWithButton(command, "editMdtodoTaskInline", ["mdtodo-body-save", "mdtodo-edit-body-save"], [{ kind: "fill", testId: ["mdtodo-body-editor", "mdtodo-edit-body"], value: body, openByDblClickTestId: ["mdtodo-body-read", "mdtodo-body-rendered"] }]);
}
if (field === "status") {
const status = commandValue(command, ["status", "text"]);
@@ -1219,7 +1229,9 @@ async function deleteMdtodoTask(command) {
const selection = await selectTaskIfCommandTargetsOne(command);
const firstClick = await clickProjectButtonAndMaybeWait("mdtodo-delete-task", null);
let confirmClick = null;
const confirmVisible = await visibleLocator(page.locator('[data-testid="mdtodo-delete-cancel"]').first());
const deleteButton = page.locator('[data-testid="mdtodo-delete-task"]').first();
const confirmVisible = await visibleLocator(page.locator('[data-testid="mdtodo-delete-cancel"]').first())
|| await deleteButton.evaluate((element) => /确认|confirm/iu.test(String(element.textContent || ""))).catch(() => false);
if (confirmVisible) confirmClick = await clickProjectButtonAndMaybeWait("mdtodo-delete-task", /^\/v1\/project-management\/mdtodo\/tasks/u);
return { beforeUrl, afterUrl: currentPageUrl(), type: "deleteMdtodoTask", selection, firstClick, confirmClick, beforeProject, afterProject: await projectManagementCommandSnapshot(), pageId, valuesRedacted: true };
}
@@ -1250,7 +1262,7 @@ async function launchWorkbenchFromTask(command) {
: null;
const providerSelection = await selectMdtodoProviderProfileForLaunch(command);
const projectBeforeClick = await projectManagementCommandSnapshot({ includeRaw: true });
const button = page.locator('[data-testid="mdtodo-workbench-launch"], [data-action="launch-workbench"]').first();
const button = page.locator('[data-testid="mdtodo-workbench-launch"], [data-testid="mdtodo-launch-workbench"], [data-action="launch-workbench"]').first();
await button.waitFor({ state: "visible", timeout: 15000 });
const buttonState = await button.evaluate((element) => ({
disabled: Boolean(element.disabled) || element.getAttribute("aria-disabled") === "true",
@@ -1392,8 +1404,8 @@ async function projectManagementCommandSnapshot(options = {}) {
selected: option.selected === true
})) : [];
const selectedFileOption = fileOptions.find((option) => option.selected) || null;
const launch = document.querySelector('[data-testid="mdtodo-workbench-launch"], [data-action="launch-workbench"]');
const bodyRendered = document.querySelector('[data-testid="mdtodo-body-rendered"]');
const launch = document.querySelector('[data-testid="mdtodo-workbench-launch"], [data-testid="mdtodo-launch-workbench"], [data-action="launch-workbench"]');
const bodyRendered = document.querySelector('[data-testid="mdtodo-body-rendered"], [data-testid="mdtodo-body-read"]');
const reportPreview = document.querySelector('[data-testid="mdtodo-report-preview"]');
const reportError = document.querySelector('[data-testid="mdtodo-report-error"]');
const reportFullscreen = document.querySelector('[data-testid="mdtodo-report-fullscreen-dialog"]');
@@ -1410,11 +1422,11 @@ async function projectManagementCommandSnapshot(options = {}) {
fileOptionLabels: fileOptions.map((option) => option.label).filter(Boolean).slice(0, 20),
selectedTaskRefRaw: selectedTask?.getAttribute("data-task-ref") || null,
selectedTaskId: selectedTask?.getAttribute("data-task-id") || selectedTask?.getAttribute("data-rxx-id") || null,
selectedTaskStatus: selectedTask?.getAttribute("data-task-status") || null,
selectedTaskStatus: selectedTask?.getAttribute("data-task-status") || selectedTask?.querySelector("[data-state]")?.getAttribute("data-state") || null,
sourceSelectVisible: visible(sourceSelect),
fileSelectVisible: visible(fileSelect),
sourceConfigVisible: visible(document.querySelector('[data-testid="mdtodo-source-form-hwpod"], [data-testid="mdtodo-source-config-dialog"], [role="dialog"]')),
taskEditorVisible: visible(document.querySelector('[data-testid="mdtodo-edit-title"], [data-testid="mdtodo-edit-body"]')),
taskEditorVisible: visible(document.querySelector('[data-testid="mdtodo-edit-title"], [data-testid="mdtodo-edit-body"], [data-testid="mdtodo-body-editor"]')),
taskBodyVisible: visible(bodyRendered),
taskBodyText: visible(bodyRendered) ? text(bodyRendered) : "",
newTaskDraftVisible: visible(document.querySelector('[data-testid="mdtodo-new-title"], [data-testid="mdtodo-new-body"]')),
@@ -1427,7 +1439,7 @@ async function projectManagementCommandSnapshot(options = {}) {
launchButtonVisible: visible(launch),
launchButtonEnabled: visible(launch) && !launch.disabled && launch.getAttribute("aria-disabled") !== "true",
launchButtonText: text(launch),
blockerTexts: Array.from(document.querySelectorAll('[data-testid="mdtodo-workbench-launch-blocker"], [data-testid="mdtodo-workbench-launch-error"], [role="alert"]')).filter(visible).map(text).filter(Boolean).slice(0, 8),
blockerTexts: Array.from(document.querySelectorAll('[data-testid="mdtodo-workbench-launch-blocker"], [data-testid="mdtodo-workbench-launch-error"], [data-testid="mdtodo-create-blocker"], .launch-blocker, [role="alert"]')).filter(visible).map(text).filter(Boolean).slice(0, 8),
workbenchLinkCount: Array.from(document.querySelectorAll('[data-testid="mdtodo-workbench-link-summary"] li, a[href*="/workbench/sessions/"]')).filter(visible).length,
valuesRedacted: true
};