Distinguishes closed listings from missing apply buttons. Shows in summary as a separate line item. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
64 lines
2.6 KiB
JavaScript
64 lines
2.6 KiB
JavaScript
/**
|
||
* wellfound.mjs — Wellfound apply handler
|
||
*/
|
||
import {
|
||
NAVIGATION_TIMEOUT, PAGE_LOAD_WAIT, FORM_FILL_WAIT, SUBMIT_WAIT
|
||
} from '../constants.mjs';
|
||
|
||
export const SUPPORTED_TYPES = ['wellfound', 'wellfound_apply'];
|
||
|
||
export async function apply(page, job, formFiller) {
|
||
await page.goto(job.url, { waitUntil: 'domcontentloaded', timeout: NAVIGATION_TIMEOUT });
|
||
await page.waitForTimeout(PAGE_LOAD_WAIT);
|
||
|
||
const meta = await page.evaluate(() => ({
|
||
title: document.querySelector('h1')?.textContent?.trim(),
|
||
company: document.querySelector('[class*="company"] h2, [class*="startup"] h2, h2')?.textContent?.trim(),
|
||
})).catch(() => ({}));
|
||
|
||
// Check if listing is closed/unavailable
|
||
const closed = await page.evaluate(() => {
|
||
const text = (document.body.innerText || '').toLowerCase();
|
||
return text.includes('no longer accepting') || text.includes('position has been filled') ||
|
||
text.includes('this job is no longer') || text.includes('job not found');
|
||
}).catch(() => false);
|
||
if (closed) {
|
||
console.log(` ℹ️ Job closed — no longer available`);
|
||
return { status: 'closed', meta };
|
||
}
|
||
|
||
const applyBtn = page.locator('a:has-text("Apply"), button:has-text("Apply Now"), a:has-text("Apply Now")').first();
|
||
if (await applyBtn.count() === 0) return { status: 'no_button', meta };
|
||
|
||
await applyBtn.click();
|
||
await page.waitForTimeout(FORM_FILL_WAIT);
|
||
|
||
const unknowns = await formFiller.fill(page, formFiller.profile.resume_path);
|
||
|
||
if (unknowns[0]?.honeypot) return { status: 'skipped_honeypot', meta };
|
||
if (unknowns.length > 0) return { status: 'needs_answer', pending_question: unknowns[0], meta };
|
||
|
||
const submitBtn = await page.$('button[type="submit"]:not([disabled]), input[type="submit"]');
|
||
if (!submitBtn) return { status: 'no_submit', meta };
|
||
|
||
await submitBtn.click();
|
||
await page.waitForTimeout(SUBMIT_WAIT);
|
||
|
||
// Verify submission — check for success indicators or form gone
|
||
const postSubmit = await page.evaluate(() => {
|
||
const text = (document.body.innerText || '').toLowerCase();
|
||
return {
|
||
hasSuccess: text.includes('application submitted') || text.includes('successfully applied') ||
|
||
text.includes('thank you') || text.includes('application received'),
|
||
hasForm: !!document.querySelector('form button[type="submit"]:not([disabled])'),
|
||
};
|
||
}).catch(() => ({ hasSuccess: false, hasForm: false }));
|
||
|
||
if (postSubmit.hasSuccess || !postSubmit.hasForm) {
|
||
return { status: 'submitted', meta };
|
||
}
|
||
|
||
console.log(` ⚠️ Submit clicked but form still present — may not have submitted`);
|
||
return { status: 'incomplete', meta };
|
||
}
|