From 1772229ae7c178c351b5ccdea075b693f659d359 Mon Sep 17 00:00:00 2001 From: Matthew Jackson Date: Fri, 6 Mar 2026 20:08:44 -0800 Subject: [PATCH] Fix CAPTCHA detection to allow invisible reCAPTCHA Invisible reCAPTCHA (size=invisible) fires on submit and usually passes automatically. Only block on visible CAPTCHA challenges. Also fix form detection to work without
wrapper elements. Co-Authored-By: Claude Opus 4.6 --- lib/apply/generic.mjs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/apply/generic.mjs b/lib/apply/generic.mjs index e7be81b..3e57194 100644 --- a/lib/apply/generic.mjs +++ b/lib/apply/generic.mjs @@ -32,7 +32,14 @@ export async function apply(page, job, formFiller) { const hasLogin = !!(document.querySelector('input[type="password"]') || (text.includes('sign in') && text.includes('create account')) || (text.includes('log in') && text.includes('register'))); - const hasCaptcha = !!(document.querySelector('iframe[src*="recaptcha"], iframe[src*="captcha"], [class*="captcha"]')); + // Only block on visible CAPTCHAs — invisible reCAPTCHA (size=invisible) fires on submit and usually passes + const captchaFrames = Array.from(document.querySelectorAll('iframe[src*="recaptcha"], iframe[src*="captcha"]')); + const hasVisibleCaptcha = captchaFrames.some(f => { + if (f.src.includes('size=invisible')) return false; + const rect = f.getBoundingClientRect(); + return rect.width > 50 && rect.height > 50; + }); + const hasCaptcha = hasVisibleCaptcha; const isClosed = text.includes('no longer accepting') || text.includes('position has been filled') || text.includes('this job is no longer') || text.includes('job not found') || text.includes('this position is closed') || text.includes('listing has expired'); @@ -44,7 +51,8 @@ export async function apply(page, job, formFiller) { if (pageCheck.hasCaptcha) return { status: 'skipped_captcha', meta }; // Some pages land directly on the form; others need an Apply button click - const hasFormAlready = await page.$('form input[type="text"], form input[type="email"], form textarea'); + // Check if we landed directly on a form (with or without wrapper) + const hasFormAlready = await page.$('input[type="text"], input[type="email"], textarea'); if (!hasFormAlready) { const applyBtn = page.locator([ 'a:has-text("Apply Now")', @@ -76,7 +84,8 @@ export async function apply(page, job, formFiller) { // Re-check for blockers after click const postClick = await page.evaluate(() => ({ hasLogin: !!document.querySelector('input[type="password"]'), - hasCaptcha: !!document.querySelector('iframe[src*="recaptcha"], [class*="captcha"]'), + hasCaptcha: Array.from(document.querySelectorAll('iframe[src*="recaptcha"], iframe[src*="captcha"]')) + .some(f => !f.src.includes('size=invisible') && f.getBoundingClientRect().width > 50), })).catch(() => ({})); if (postClick.hasLogin) return { status: 'skipped_login_required', meta }; if (postClick.hasCaptcha) return { status: 'skipped_captcha', meta };