The patrol-delete flow now waits for country and listing-status controls before acting, uses the dedicated PatrolDeleteTask entry point, and includes the browser helper scripts required by the bulk-delete path. Tests cover delayed dropdown readiness, payload rejection/retry behavior, complete-draft normalization, filtered bulk deletion, and asset fallback lookup. Constraint: Amazon listing UI exposes status controls through dynamic KAT components and delayed option rendering Rejected: Keep row-by-row delete flow | bulk selection is the current implemented path and is covered by the added helper scripts Confidence: high Scope-risk: moderate Directive: Do not remove the patrol_delete JS helper files without checking app/amazon/patrol_delete.py script loading Tested: uv run --group dev pytest tests\\test_amazon_base.py tests\\test_patrol_delete.py Tested: uv run python -m py_compile amazon\\base.py amazon\\main.py blueprints\\main.py Not-tested: Real Amazon Seller Central browser session
47 lines
1.6 KiB
JavaScript
47 lines
1.6 KiB
JavaScript
function textOf(node) {
|
|
return ((node && (node.innerText || node.textContent || '')) || '').replace(/\s+/g, ' ').trim();
|
|
}
|
|
|
|
function attrsText(el) {
|
|
if (!el || !el.attributes) return '';
|
|
return Array.from(el.attributes).map((attr) => `${attr.name}=${attr.value}`).join(' ');
|
|
}
|
|
|
|
function isVisible(el) {
|
|
if (!el || !el.getBoundingClientRect) return false;
|
|
const rect = el.getBoundingClientRect();
|
|
const style = window.getComputedStyle(el);
|
|
return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden';
|
|
}
|
|
|
|
function walkRoots(root, rows, seen) {
|
|
if (!root || seen.has(root)) return;
|
|
seen.add(root);
|
|
|
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
let node = walker.currentNode;
|
|
while (node) {
|
|
if (node.nodeType === Node.ELEMENT_NODE && node.tagName) rows.push(node);
|
|
if (node.shadowRoot) walkRoots(node.shadowRoot, rows, seen);
|
|
if ((node.tagName || '').toLowerCase() === 'slot' && node.assignedElements) {
|
|
for (const assigned of node.assignedElements({ flatten: true })) {
|
|
walkRoots(assigned, rows, seen);
|
|
}
|
|
}
|
|
node = walker.nextNode();
|
|
}
|
|
}
|
|
|
|
const rows = [];
|
|
walkRoots(document, rows, new Set());
|
|
|
|
const seen = new Set();
|
|
return rows
|
|
.filter((el) => {
|
|
const haystack = `${el.tagName || ''} ${attrsText(el)} ${textOf(el)}`;
|
|
return isVisible(el) && /kat-alert|alert|toast|notification|已删除|已经删除|15\s*分钟|success/i.test(haystack);
|
|
})
|
|
.map((el) => textOf(el) || attrsText(el))
|
|
.filter((text) => text && !seen.has(text) && seen.add(text))
|
|
.slice(0, 30);
|