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:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,3 +16,9 @@ config/search_config.json
|
|||||||
|
|
||||||
# Templates are committed instead (see config/*.example.json)
|
# Templates are committed instead (see config/*.example.json)
|
||||||
data/*.lock
|
data/*.lock
|
||||||
|
test_dry_run.mjs
|
||||||
|
test_full_flow.mjs
|
||||||
|
test_modal_diag.mjs
|
||||||
|
test_labels.mjs
|
||||||
|
test_buttons.mjs
|
||||||
|
test_submit.mjs
|
||||||
|
|||||||
@@ -273,20 +273,72 @@ export async function apply(page, job, formFiller) {
|
|||||||
if (submitBtn) {
|
if (submitBtn) {
|
||||||
console.log(` [step ${step}] clicking Submit`);
|
console.log(` [step ${step}] clicking Submit`);
|
||||||
await submitBtn.click({ timeout: APPLY_CLICK_TIMEOUT }).catch(() => {});
|
await submitBtn.click({ timeout: APPLY_CLICK_TIMEOUT }).catch(() => {});
|
||||||
await page.waitForTimeout(SUBMIT_WAIT);
|
|
||||||
|
|
||||||
// Verify modal closed
|
// Wait for modal to close — LinkedIn may take a few seconds after submit
|
||||||
const modalGone = !(await page.$(MODAL));
|
const modalClosed = await page.waitForSelector(MODAL, { state: 'detached', timeout: 8000 }).then(() => true).catch(() => false);
|
||||||
if (modalGone) {
|
if (modalClosed) {
|
||||||
console.log(` ✅ Submit confirmed — modal closed`);
|
console.log(` ✅ Submit confirmed — modal closed`);
|
||||||
return { status: 'submitted', meta };
|
return { status: 'submitted', meta };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modal still open — submit may have failed
|
// Modal still open — LinkedIn often shows a post-submit confirmation/success
|
||||||
console.log(` [step ${step}] ⚠️ Modal still open after Submit click`);
|
// dialog that still matches [role="dialog"]. Check for success indicators.
|
||||||
console.log(` Action: submit may have failed due to validation or network error`);
|
const postSubmitModal = await page.$(MODAL);
|
||||||
await dismissModal(page, MODAL);
|
if (postSubmitModal) {
|
||||||
return { status: 'incomplete', meta };
|
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
|
// Stuck detection — no Next/Review/Submit found
|
||||||
|
|||||||
Reference in New Issue
Block a user