Fix submit verification: wait for modal detach instead of fixed sleep

Replace fixed 2.5s sleep + single check with event-driven waitForSelector
(state: 'detached', timeout: 8s). If modal persists, check for success
indicators (success text, Submit button gone) before marking incomplete.
Fixes false 'incomplete' status when LinkedIn shows post-submit dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 11:23:03 -08:00
parent 51a4231b5d
commit 7f8cc3658e
2 changed files with 67 additions and 9 deletions

6
.gitignore vendored
View File

@@ -16,3 +16,9 @@ config/search_config.json
# Templates are committed instead (see config/*.example.json)
data/*.lock
test_dry_run.mjs
test_full_flow.mjs
test_modal_diag.mjs
test_labels.mjs
test_buttons.mjs
test_submit.mjs

View File

@@ -273,20 +273,72 @@ export async function apply(page, job, formFiller) {
if (submitBtn) {
console.log(` [step ${step}] clicking Submit`);
await submitBtn.click({ timeout: APPLY_CLICK_TIMEOUT }).catch(() => {});
await page.waitForTimeout(SUBMIT_WAIT);
// Verify modal closed
const modalGone = !(await page.$(MODAL));
if (modalGone) {
// Wait for modal to close — LinkedIn may take a few seconds after submit
const modalClosed = await page.waitForSelector(MODAL, { state: 'detached', timeout: 8000 }).then(() => true).catch(() => false);
if (modalClosed) {
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 };
// Modal still open — LinkedIn often shows a post-submit confirmation/success
// dialog that still matches [role="dialog"]. Check for success indicators.
const postSubmitModal = await page.$(MODAL);
if (postSubmitModal) {
const postSubmitInfo = await postSubmitModal.evaluate(el => {
const text = (el.innerText || el.textContent || '').toLowerCase();
return {
hasSuccess: text.includes('application was sent') || text.includes('applied') ||
text.includes('thank you') || text.includes('submitted') ||
text.includes('application has been') || text.includes('successfully'),
hasDone: text.includes('done') || text.includes('got it'),
snippet: (el.innerText || '').trim().slice(0, 200),
};
}).catch(() => ({ hasSuccess: false, hasDone: false, snippet: '' }));
console.log(` [step ${step}] post-submit modal: "${postSubmitInfo.snippet}"`);
if (postSubmitInfo.hasSuccess || postSubmitInfo.hasDone) {
console.log(` ✅ Submit confirmed — success dialog detected`);
// Try to dismiss the success dialog
const doneBtn = await findModalButton(page, MODAL, {
ariaLabels: ['Dismiss', 'Done', 'Close'],
exactTexts: ['Done', 'Got it', 'Close'],
});
if (doneBtn) await doneBtn.click().catch(() => {});
return { status: 'submitted', meta };
}
// Check for validation errors — real failure
const postErrors = await postSubmitModal.$$('[class*="error"], [aria-invalid="true"], .artdeco-inline-feedback--error');
const errorTexts = [];
for (const e of postErrors) {
const t = await e.evaluate(el => el.textContent?.trim()?.slice(0, 80) || '').catch(() => '');
if (t) errorTexts.push(t);
}
if (errorTexts.length > 0) {
console.log(` [step ${step}] ❌ Validation errors after Submit: ${JSON.stringify(errorTexts)}`);
await dismissModal(page, MODAL);
return { status: 'incomplete', meta, validation_errors: errorTexts };
}
// No errors, no success text — but Submit button is gone, likely succeeded
// (LinkedIn sometimes shows a follow-up prompt like "Follow company?")
const submitStillThere = await findModalButton(page, MODAL, {
ariaLabels: ['Submit application'],
exactTexts: ['Submit application'],
});
if (!submitStillThere) {
console.log(` ✅ Submit likely succeeded — Submit button gone, no errors`);
await dismissModal(page, MODAL);
return { status: 'submitted', meta };
}
console.log(` [step ${step}] ⚠️ Submit button still present — click may not have registered`);
await dismissModal(page, MODAL);
return { status: 'incomplete', meta };
}
}
// Stuck detection — no Next/Review/Submit found