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 <form> wrapper elements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 20:08:44 -08:00
parent 260f996ebc
commit 1772229ae7

View File

@@ -32,7 +32,14 @@ export async function apply(page, job, formFiller) {
const hasLogin = !!(document.querySelector('input[type="password"]') || const hasLogin = !!(document.querySelector('input[type="password"]') ||
(text.includes('sign in') && text.includes('create account')) || (text.includes('sign in') && text.includes('create account')) ||
(text.includes('log in') && text.includes('register'))); (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') || 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 job is no longer') || text.includes('job not found') ||
text.includes('this position is closed') || text.includes('listing has expired'); 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 }; if (pageCheck.hasCaptcha) return { status: 'skipped_captcha', meta };
// Some pages land directly on the form; others need an Apply button click // 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 <form> wrapper)
const hasFormAlready = await page.$('input[type="text"], input[type="email"], textarea');
if (!hasFormAlready) { if (!hasFormAlready) {
const applyBtn = page.locator([ const applyBtn = page.locator([
'a:has-text("Apply Now")', 'a:has-text("Apply Now")',
@@ -76,7 +84,8 @@ export async function apply(page, job, formFiller) {
// Re-check for blockers after click // Re-check for blockers after click
const postClick = await page.evaluate(() => ({ const postClick = await page.evaluate(() => ({
hasLogin: !!document.querySelector('input[type="password"]'), 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(() => ({})); })).catch(() => ({}));
if (postClick.hasLogin) return { status: 'skipped_login_required', meta }; if (postClick.hasLogin) return { status: 'skipped_login_required', meta };
if (postClick.hasCaptcha) return { status: 'skipped_captcha', meta }; if (postClick.hasCaptcha) return { status: 'skipped_captcha', meta };