From 5538215650bec3c91183fcaa41f86eaec298a619 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Mon, 31 Aug 2026 00:41:30 -0500 Subject: [PATCH] Load every failed account before the Tradeify reset loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The account list loads a screenful at a time, so a reset count larger than the first page ran out of rows to click. Counts the reset buttons rather than the rows, so the run log reports the number that answers the question — are there enough accounts to reset `count` times. A stretch of non-resettable rows does not stop it early: `stable` only advances when the scroller was ALREADY at the bottom and nothing new matched, and every appended batch pushes scrollHeight past scrollTop + clientHeight. Simulated at 100 rows with the resettables in the middle, at the bottom, and at the top only — all four layouts walk the whole list. The container is named rather than guessed. The list scrolls inside dashboard_body, not the document, and setting scrollTop on the wrong element scrolls nothing at all — which fails as a low match count rather than an error. Spelled as a selector list because the live markup is unconfirmed: querySelector takes either form, and matching neither fails loudly. Co-Authored-By: Claude Opus 5 --- lib/automations.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/automations.ts b/lib/automations.ts index 13c334f..27186b1 100644 --- a/lib/automations.ts +++ b/lib/automations.ts @@ -164,9 +164,14 @@ export const FIRMS: Firm[] = [ // every site build, so they are not safe to select on. { action: 'click', selector: 'a.add_account_btn', label: 'Open Add Account' }, - + { action: 'waitFor', selector: 'div.account_types:nth-child(3) > div[role="radiogroup"] > div > div:nth-child(2)', timeoutSeconds: 30, label: 'Wait for form' }, + { action: 'click', selector: 'div.account_types:nth-child(3) > div[role="radiogroup"] > div > div:nth-child(2)'}, + { action: 'click', selector: 'div.account_types:nth-child(7) span:last-child'}, + { action: 'click', selector: 'div.summary_section div.MuiTextField-root input'}, + { action: 'type', selector: 'div.summary_section div.MuiTextField-root input', text: 'MX8'}, + { action: 'click', selector: 'div.summary_section div.MuiTextField-root + button'}, { action: 'solveCaptcha', timeoutSeconds: 180, label: 'Solve the challenge' }, - + { action: 'click', selector: 'div.summary_section > button:last-child', label: 'Open Add Account' }, // TODO: the rest of the purchase flow. Confirm each selector with // `clicker.py locate` before adding it here. @@ -184,6 +189,25 @@ export const FIRMS: Firm[] = [ { action: 'click', selector: 'div.MuiTabs-scroller button:nth-child(2)', label: 'Click Evaluation Tab'}, { action: 'click', selector: '.tab-head-right .MuiSwitch-colorPrimary.Mui-checked', skipIfNotFound: true, label: 'Show failed accounts' }, + // The account list loads a screenful at a time, so a reset + // count larger than the first page would run out of rows to + // click. Walk it to the bottom first. Counting the reset + // buttons rather than the rows means it stops when the thing + // the loop actually clicks stops appearing. + // + // The list scrolls inside dashboard_body, not the document — + // the automatic guess walks up from a match looking for an + // overflow ancestor, and setting scrollTop on the wrong + // element scrolls nothing at all. Named here so it cannot + // guess wrong. Trim the pair once you know whether the site + // spells it as a class or an id. + { + action: 'scrollToLoad', + selector: '.status-failed button.reset_btn', + containerSelector: '.dashboard_body, #dashboard_body', + label: 'Load every failed account', + }, + { action: 'repeat', timesFrom: 'count', steps: [ // one purchase — the steps you already have { action: 'click', selector: '.status-failed button.reset_btn', label: 'Reset Account' },