From 5e4a0c65996bee23146338250a3e1186d1bc6bae Mon Sep 17 00:00:00 2001 From: Matthew Jackson Date: Fri, 6 Mar 2026 10:34:19 -0800 Subject: [PATCH] fix: always discard on modal dismiss, support "Continue applying" button - dismissModal now always waits for Discard confirmation after closing (previously returned early after Dismiss click, leaving draft saved) - LINKEDIN_APPLY_BUTTON_SELECTOR matches both "Easy Apply" and "Continue applying" (shown when a previous draft exists) Co-Authored-By: Claude Opus 4.6 --- lib/apply/easy_apply.mjs | 26 ++++++++++++-------------- lib/constants.mjs | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/apply/easy_apply.mjs b/lib/apply/easy_apply.mjs index e3d8018..e340143 100644 --- a/lib/apply/easy_apply.mjs +++ b/lib/apply/easy_apply.mjs @@ -267,39 +267,37 @@ export async function apply(page, job, formFiller) { * All searches use page.$() which pierces shadow DOM. */ async function dismissModal(page, modalSelector) { - // Try aria-label Dismiss + // Step 1: Close the modal — Dismiss button, Close/X, or Escape const dismissBtn = await page.$(`${modalSelector} button[aria-label="Dismiss"]`); if (dismissBtn) { await dismissBtn.click({ timeout: DISMISS_TIMEOUT }).catch(() => {}); - return; + } else { + const closeBtn = await page.$(`${modalSelector} button[aria-label="Close"], ${modalSelector} button[aria-label*="close"]`); + if (closeBtn) { + await closeBtn.click({ timeout: DISMISS_TIMEOUT }).catch(() => {}); + } else { + await page.keyboard.press('Escape').catch(() => {}); + } } - // Try close/X button - const closeBtn = await page.$(`${modalSelector} button[aria-label="Close"], ${modalSelector} button[aria-label*="close"]`); - if (closeBtn) { - await closeBtn.click({ timeout: DISMISS_TIMEOUT }).catch(() => {}); - return; - } - - // Fallback: Escape key - await page.keyboard.press('Escape').catch(() => {}); - - // Handle "Discard" confirmation dialog that may appear after Escape + // Step 2: LinkedIn shows a "Discard" confirmation — always wait for it and click const discardBtn = await page.waitForSelector( 'button[data-test-dialog-primary-btn]', { timeout: DISMISS_TIMEOUT, state: 'visible' } ).catch(() => null); if (discardBtn) { await discardBtn.click().catch(() => {}); + await page.waitForTimeout(500); return; } - // Last resort: find Discard by text — scan all buttons via page.$$() + // Fallback: find Discard by text — scan all buttons via page.$$() const allBtns = await page.$$('button'); for (const btn of allBtns) { const text = await btn.evaluate(el => (el.innerText || '').trim().toLowerCase()).catch(() => ''); if (text === 'discard') { await btn.click().catch(() => {}); + await page.waitForTimeout(500); return; } } diff --git a/lib/constants.mjs b/lib/constants.mjs index e45c1fa..0a5513b 100644 --- a/lib/constants.mjs +++ b/lib/constants.mjs @@ -24,7 +24,7 @@ export const APPLY_BETWEEN_DELAY_JITTER = 1000; // --- LinkedIn --- export const LINKEDIN_BASE = 'https://www.linkedin.com'; export const LINKEDIN_EASY_APPLY_MODAL_SELECTOR = '[role="dialog"]'; -export const LINKEDIN_APPLY_BUTTON_SELECTOR = '[aria-label*="Easy Apply"]'; +export const LINKEDIN_APPLY_BUTTON_SELECTOR = '[aria-label*="Easy Apply"], [aria-label*="Continue applying"]'; export const LINKEDIN_MAX_MODAL_STEPS = 20; // --- Wellfound ---