Decouple form filler from LinkedIn modal selector
Form filler now defaults to page root instead of scoping to [role="dialog"]. LinkedIn Easy Apply passes its modal selector explicitly. Fixes external ATS forms being scoped to wrong container. Also improved Greenhouse handler with targeted resume upload and form detection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -212,7 +212,7 @@ export async function apply(page, job, formFiller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fill form fields — page.$() in form_filler pierces shadow DOM
|
// Fill form fields — page.$() in form_filler pierces shadow DOM
|
||||||
const unknowns = await formFiller.fill(page, formFiller.profile.resume_path);
|
const unknowns = await formFiller.fill(page, formFiller.profile.resume_path, { modalSelector: MODAL });
|
||||||
if (unknowns.length > 0) console.log(` [step ${step}] unknown fields: ${JSON.stringify(unknowns.map(u => u.label || u))}`);
|
if (unknowns.length > 0) console.log(` [step ${step}] unknown fields: ${JSON.stringify(unknowns.map(u => u.label || u))}`);
|
||||||
|
|
||||||
if (unknowns[0]?.honeypot) {
|
if (unknowns[0]?.honeypot) {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* greenhouse.mjs — Greenhouse ATS handler (extends generic)
|
* greenhouse.mjs — Greenhouse ATS handler (extends generic)
|
||||||
|
*
|
||||||
|
* Greenhouse boards show the form directly on the page (no Apply button needed).
|
||||||
|
* Form ID: #application-form. Resume input: #resume. Submit: "Submit application".
|
||||||
*/
|
*/
|
||||||
import { apply as genericApply } from './generic.mjs';
|
import { apply as genericApply } from './generic.mjs';
|
||||||
|
|
||||||
@@ -7,6 +10,19 @@ export const SUPPORTED_TYPES = ['greenhouse'];
|
|||||||
|
|
||||||
export async function apply(page, job, formFiller) {
|
export async function apply(page, job, formFiller) {
|
||||||
return genericApply(page, job, formFiller, {
|
return genericApply(page, job, formFiller, {
|
||||||
submitSelector: 'button:has-text("Submit Application"), input[type="submit"]',
|
formDetector: '#application-form',
|
||||||
|
submitSelector: 'button:has-text("Submit application"), input[type="submit"]',
|
||||||
|
verifySelector: '#application-form',
|
||||||
|
beforeSubmit: async (page, formFiller) => {
|
||||||
|
if (!formFiller.profile.resume_path) return;
|
||||||
|
const resumeInput = await page.$('#resume');
|
||||||
|
if (resumeInput) {
|
||||||
|
const hasFile = await resumeInput.evaluate(el => !!el.value);
|
||||||
|
if (!hasFile) {
|
||||||
|
await resumeInput.setInputFiles(formFiller.profile.resume_path).catch(() => {});
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { writeFileSync, renameSync } from 'fs';
|
|||||||
import {
|
import {
|
||||||
DEFAULT_YEARS_EXPERIENCE, DEFAULT_DESIRED_SALARY,
|
DEFAULT_YEARS_EXPERIENCE, DEFAULT_DESIRED_SALARY,
|
||||||
MINIMUM_SALARY_FACTOR, DEFAULT_SKILL_RATING,
|
MINIMUM_SALARY_FACTOR, DEFAULT_SKILL_RATING,
|
||||||
LINKEDIN_EASY_APPLY_MODAL_SELECTOR, FORM_PATTERN_MAX_LENGTH,
|
FORM_PATTERN_MAX_LENGTH,
|
||||||
AUTOCOMPLETE_WAIT, AUTOCOMPLETE_TIMEOUT, ANTHROPIC_API_URL, DEFAULT_MODEL
|
AUTOCOMPLETE_WAIT, AUTOCOMPLETE_TIMEOUT, ANTHROPIC_API_URL, DEFAULT_MODEL
|
||||||
} from './constants.mjs';
|
} from './constants.mjs';
|
||||||
|
|
||||||
@@ -553,9 +553,9 @@ Answer:`;
|
|||||||
* then only makes individual CDP calls for elements that need action.
|
* then only makes individual CDP calls for elements that need action.
|
||||||
* Returns array of unknown required field labels.
|
* Returns array of unknown required field labels.
|
||||||
*/
|
*/
|
||||||
async fill(page, resumePath) {
|
async fill(page, resumePath, { modalSelector = null } = {}) {
|
||||||
const unknown = [];
|
const unknown = [];
|
||||||
const container = await page.$(LINKEDIN_EASY_APPLY_MODAL_SELECTOR) || page;
|
const container = modalSelector ? (await page.$(modalSelector) || page) : page;
|
||||||
|
|
||||||
// Single DOM snapshot — all labels, values, visibility, required status
|
// Single DOM snapshot — all labels, values, visibility, required status
|
||||||
const snap = await this._snapshotFields(container);
|
const snap = await this._snapshotFields(container);
|
||||||
|
|||||||
Reference in New Issue
Block a user