fix: cover letter silent drop, submit verification, autocomplete scoping

- Text inputs matched to cover letter now report as unknown if required,
  instead of silently leaving the field empty
- Submit click now verifies modal closed before reporting success;
  returns 'incomplete' with actionable log if modal stays open
- selectAutocomplete scoped to container (modal) to avoid clicking
  wrong dropdowns from the underlying page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 10:11:44 -08:00
parent e62756c6ca
commit e6fb380e1c
2 changed files with 28 additions and 9 deletions

View File

@@ -207,7 +207,20 @@ export async function apply(page, job, formFiller) {
console.log(` [step ${step}] clicking Submit`);
await submitBtn.click({ timeout: APPLY_CLICK_TIMEOUT }).catch(() => {});
await page.waitForTimeout(SUBMIT_WAIT);
return { status: 'submitted', meta };
// Verify modal closed or success message appeared
const modalGone = !(await page.$(MODAL));
const successVisible = await page.$('[class*="success"], [class*="confirmation"], [aria-label*="applied"]').catch(() => null);
if (modalGone || successVisible) {
console.log(` ✅ Submit confirmed — modal closed`);
return { status: 'submitted', meta };
}
// Modal still open — submit may have failed
console.log(` [step ${step}] ⚠️ Modal still open after Submit click`);
console.log(` Action: submit may have failed due to validation or network error`);
await dismissModal(page, MODAL);
return { status: 'incomplete', meta };
}
// Stuck detection — progress hasn't changed and we've been through a few steps

View File

@@ -148,12 +148,17 @@ export class FormFiller {
* Waits for the dropdown to appear, then clicks the first option.
* Scoped to the input's nearest container to avoid clicking wrong dropdowns.
*/
async selectAutocomplete(page, inp) {
// Wait for dropdown to appear near the input
const option = await page.waitForSelector(
'[role="option"], [role="listbox"] li, ul[class*="autocomplete"] li',
{ timeout: AUTOCOMPLETE_TIMEOUT, state: 'visible' }
).catch(() => null);
async selectAutocomplete(page, container) {
// Wait for dropdown to appear — scope to container (modal) to avoid clicking wrong dropdowns
const selectors = '[role="option"], [role="listbox"] li, ul[class*="autocomplete"] li';
const option = await container.waitForSelector(selectors, {
timeout: AUTOCOMPLETE_TIMEOUT, state: 'visible',
}).catch(() => {
// Fallback to page-level if container doesn't support waitForSelector (e.g. ElementHandle)
return page.waitForSelector(selectors, {
timeout: AUTOCOMPLETE_TIMEOUT, state: 'visible',
}).catch(() => null);
});
if (option) {
await option.click().catch(() => {});
await page.waitForTimeout(AUTOCOMPLETE_WAIT);
@@ -198,9 +203,10 @@ export class FormFiller {
// Handle city/location autocomplete dropdowns
const ll = lbl.toLowerCase();
if (ll.includes('city') || ll.includes('location') || ll.includes('located')) {
await this.selectAutocomplete(page, inp);
await this.selectAutocomplete(page, container);
}
} else if (!answer) {
} else {
// No answer, or answer is cover letter (too long for a text input) — check if required
if (await this.isRequired(inp)) unknown.push(lbl);
}
}