diff --git a/lib/apply/easy_apply.mjs b/lib/apply/easy_apply.mjs
index 34151af..33d7fa2 100644
--- a/lib/apply/easy_apply.mjs
+++ b/lib/apply/easy_apply.mjs
@@ -115,22 +115,25 @@ export async function apply(page, job, formFiller) {
let eaBtn = await page.waitForSelector(LINKEDIN_APPLY_BUTTON_SELECTOR, { timeout: 12000, state: 'attached' }).catch(() => null);
// Fallback: LinkedIn shows plain "Continue" when a draft exists (span > span > a)
- // The has href containing /apply/ — click it to resume the draft
+ // The has href containing /apply/ — find it via evaluateHandle in each frame
+ // (page.$() may not pierce LinkedIn's specific shadow DOM setup)
if (!eaBtn) {
- // page.$() pierces shadow DOM; also check via evaluate in each frame
- let applyLink = await page.$(`a[href*="/apply/"]`);
- if (!applyLink) {
- // Search frames directly (shadow DOM may block page.$)
- for (const frame of page.frames()) {
- applyLink = await frame.$(`a[href*="/apply/"]`).catch(() => null);
- if (applyLink) break;
- }
- }
- if (applyLink) {
- const text = await applyLink.evaluate(el => (el.innerText || '').trim()).catch(() => '');
- if (/continue/i.test(text)) {
- eaBtn = applyLink;
- console.log(` ℹ️ Found "Continue" link (draft application)`);
+ for (const frame of page.frames()) {
+ const handle = await frame.evaluateHandle(() => {
+ const links = document.querySelectorAll('a[href*="/apply/"]');
+ for (const a of links) {
+ if (/continue/i.test((a.innerText || '').trim())) return a;
+ }
+ return null;
+ }).catch(() => null);
+ if (handle) {
+ const el = handle.asElement();
+ if (el) {
+ eaBtn = el;
+ console.log(` ℹ️ Found "Continue" link (draft application)`);
+ break;
+ }
+ await handle.dispose().catch(() => {});
}
}
}