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
72 lines
2.2 KiB
JavaScript
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),
|
|
},
|
|
};
|