Files
crawler-plugin/app/amazon/scripts/patrol_delete/inventory_select_all.js
koko 1be28f10e9 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
2026-04-28 20:46:50 +08:00

72 lines
2.2 KiB
JavaScript

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),
},
};