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:
71
app/amazon/scripts/patrol_delete/inventory_select_all.js
Normal file
71
app/amazon/scripts/patrol_delete/inventory_select_all.js
Normal file
@@ -0,0 +1,71 @@
|
||||
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);
|
||||
node = walker.nextNode();
|
||||
}
|
||||
}
|
||||
|
||||
function allElements() {
|
||||
const rows = [];
|
||||
walkRoots(document, rows, new Set());
|
||||
return rows;
|
||||
}
|
||||
|
||||
function insideInventoryRow(el) {
|
||||
return Boolean(el && el.closest && el.closest('div[data-sku]'));
|
||||
}
|
||||
|
||||
function clickTargetFor(checkbox) {
|
||||
if (!checkbox) return null;
|
||||
if (checkbox.shadowRoot) {
|
||||
const shadowTarget = checkbox.shadowRoot.querySelector('[role="checkbox"], .checkbox');
|
||||
if (shadowTarget) return shadowTarget;
|
||||
}
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
const rowCount = document.querySelectorAll('div[data-sku]').length;
|
||||
if (rowCount <= 0) {
|
||||
return { ok: false, reason: 'no inventory rows after status filter', rowCount };
|
||||
}
|
||||
|
||||
const checkboxes = allElements()
|
||||
.filter((el) => (el.tagName || '').toLowerCase() === 'kat-checkbox' && isVisible(el))
|
||||
.sort((a, b) => {
|
||||
const rectA = a.getBoundingClientRect();
|
||||
const rectB = b.getBoundingClientRect();
|
||||
return rectA.top - rectB.top || rectA.left - rectB.left;
|
||||
});
|
||||
|
||||
const headerCheckbox = checkboxes.find((el) => !insideInventoryRow(el)) || checkboxes[0];
|
||||
if (!headerCheckbox) {
|
||||
return { ok: false, reason: 'select-all checkbox not found', rowCount, checkboxCount: checkboxes.length };
|
||||
}
|
||||
|
||||
const target = clickTargetFor(headerCheckbox);
|
||||
target.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
target.click();
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
rowCount,
|
||||
checkboxCount: checkboxes.length,
|
||||
clicked: {
|
||||
tag: headerCheckbox.tagName,
|
||||
insideInventoryRow: insideInventoryRow(headerCheckbox),
|
||||
outerHTML: (headerCheckbox.outerHTML || '').slice(0, 1000),
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user