Fix Ashby resume upload and add validation error logging
Ashby wraps file input inside #_systemfield_resume container — search for the actual input[type="file"] element. Also capture and log validation errors from the page when submit returns incomplete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -399,6 +399,9 @@ async function handleResult(job, result, results, settings, profile, apiKey) {
|
|||||||
|
|
||||||
case 'stuck':
|
case 'stuck':
|
||||||
case 'incomplete': {
|
case 'incomplete': {
|
||||||
|
if (result.validation_errors?.length) {
|
||||||
|
console.log(` ⚠️ Validation errors: ${result.validation_errors.join(' | ')}`);
|
||||||
|
}
|
||||||
const retries = (job.retry_count || 0) + 1;
|
const retries = (job.retry_count || 0) + 1;
|
||||||
const maxRetry = settings.max_retries ?? DEFAULT_MAX_RETRIES;
|
const maxRetry = settings.max_retries ?? DEFAULT_MAX_RETRIES;
|
||||||
if (retries <= maxRetry) {
|
if (retries <= maxRetry) {
|
||||||
|
|||||||
@@ -13,10 +13,14 @@ export async function apply(page, job, formFiller) {
|
|||||||
submitSelector: 'button:has-text("Submit Application")',
|
submitSelector: 'button:has-text("Submit Application")',
|
||||||
verifySelector: '#_systemfield_name',
|
verifySelector: '#_systemfield_name',
|
||||||
beforeSubmit: async (page, formFiller) => {
|
beforeSubmit: async (page, formFiller) => {
|
||||||
const resumeInput = await page.$('#_systemfield_resume');
|
if (!formFiller.profile.resume_path) return;
|
||||||
if (resumeInput && formFiller.profile.resume_path) {
|
// Ashby wraps resume upload in a custom component — find the actual file input
|
||||||
await resumeInput.setInputFiles(formFiller.profile.resume_path).catch(() => {});
|
const fileInput = await page.$('#_systemfield_resume input[type="file"]') ||
|
||||||
await page.waitForTimeout(1000);
|
await page.$('input[type="file"][name*="resume"]') ||
|
||||||
|
await page.$('input[type="file"]');
|
||||||
|
if (fileInput) {
|
||||||
|
await fileInput.setInputFiles(formFiller.profile.resume_path).catch(() => {});
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -154,19 +154,29 @@ async function fillAndSubmit(page, job, formFiller, meta, opts) {
|
|||||||
const verifySelector = opts.verifySelector || 'form button[type="submit"]:not([disabled])';
|
const verifySelector = opts.verifySelector || 'form button[type="submit"]:not([disabled])';
|
||||||
const postSubmit = await page.evaluate((vs) => {
|
const postSubmit = await page.evaluate((vs) => {
|
||||||
const text = (document.body.innerText || '').toLowerCase();
|
const text = (document.body.innerText || '').toLowerCase();
|
||||||
|
// Check for validation errors (red text, error messages)
|
||||||
|
const errorEls = document.querySelectorAll('[class*="error"], [class*="invalid"], [role="alert"]');
|
||||||
|
const validationErrors = Array.from(errorEls)
|
||||||
|
.map(el => el.innerText?.trim())
|
||||||
|
.filter(t => t && t.length < 200)
|
||||||
|
.slice(0, 3);
|
||||||
return {
|
return {
|
||||||
hasSuccess: text.includes('application submitted') || text.includes('successfully applied') ||
|
hasSuccess: text.includes('application submitted') || text.includes('successfully applied') ||
|
||||||
text.includes('thank you') || text.includes('application received') ||
|
text.includes('thank you') || text.includes('application received') ||
|
||||||
text.includes('application has been') || text.includes('we received your'),
|
text.includes('application has been') || text.includes('we received your'),
|
||||||
hasForm: !!document.querySelector(vs),
|
hasForm: !!document.querySelector(vs),
|
||||||
|
validationErrors,
|
||||||
};
|
};
|
||||||
}, verifySelector).catch(() => ({ hasSuccess: false, hasForm: false }));
|
}, verifySelector).catch(() => ({ hasSuccess: false, hasForm: false, validationErrors: [] }));
|
||||||
|
|
||||||
if (postSubmit.hasSuccess || !postSubmit.hasForm) {
|
if (postSubmit.hasSuccess || !postSubmit.hasForm) {
|
||||||
return { status: 'submitted', meta };
|
return { status: 'submitted', meta };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status: 'incomplete', meta };
|
return {
|
||||||
|
status: 'incomplete', meta,
|
||||||
|
...(postSubmit.validationErrors?.length && { validation_errors: postSubmit.validationErrors }),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-step: Next/Continue
|
// Multi-step: Next/Continue
|
||||||
|
|||||||
Reference in New Issue
Block a user