Stabilize patrol delete automation against delayed Amazon UI
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
This commit is contained in:
46
app/amazon/scripts/patrol_delete/delete_success_messages.js
Normal file
46
app/amazon/scripts/patrol_delete/delete_success_messages.js
Normal file
@@ -0,0 +1,46 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user