← back to Eur Recrawl
login-osborne.mjs
59 lines
// login-osborne.mjs — Osborne & Little trade login via www.osborneandlittle.com "Trade Login".
// Creds from /tmp/osb-creds.txt (user\npass). ENGINE=chrome|webkit (default chrome).
import { chromium, webkit } from 'playwright';
import fs from 'node:fs';
import path from 'node:path';
const [user, pass] = fs.readFileSync('/tmp/osb-creds.txt', 'utf8').trim().split('\n');
const ENGINE = (process.env.ENGINE || 'chrome').toLowerCase();
const drv = ENGINE === 'webkit' ? webkit : chromium;
const opts = { headless: false, viewport: { width: 1440, height: 900 } };
if (ENGINE === 'chrome') opts.channel = 'chrome';
const ctx = await drv.launchPersistentContext(path.join(process.cwd(), '.auth', `osborne-${ENGINE}`), opts);
let page = ctx.pages()[0] || await ctx.newPage();
ctx.on('page', (np) => { console.log(' [new tab]', np.url()); page = np; });
await page.goto('https://www.osborneandlittle.com/', { waitUntil: 'domcontentloaded', timeout: 60000 }).catch((e) => console.log('nav', e.message));
await page.waitForTimeout(3500);
console.log('home:', (await page.title().catch(() => '')).slice(0, 45), '@', page.url());
// dismiss cookie consent (OneTrust + generic) so it doesn't cover the Trade Login link
for (const sel of ['#onetrust-accept-btn-handler', 'button:has-text("Accept All Cookies")', 'button:has-text("Accept All")', 'button:has-text("Accept")']) {
const b = await page.$(sel).catch(() => null);
if (b) { await b.click({ timeout: 3000 }).catch(() => {}); console.log('cookie dismissed via', sel); await page.waitForTimeout(800); break; }
}
console.log('opening Trade Login panel (JS click)...');
await page.evaluate(() => { const el = [...document.querySelectorAll('a,button')].find((e) => /trade\s*login/i.test(e.innerText || '')); if (el) el.click(); }).catch(() => {});
await page.waitForTimeout(2500);
async function forceFill(sel, val, label) {
try { await page.fill(sel, val, { timeout: 8000 }); console.log(`${label}: filled`); return true; }
catch {
const ok = await page.evaluate(({ sel, val }) => { const el = document.querySelector(sel); if (!el) return false; el.value = val; el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); return true; }, { sel, val }).catch(() => false);
console.log(`${label}: ${ok ? 'filled(js)' : 'NOT FOUND'}`); return ok;
}
}
await forceFill('#inputEmail', user, 'user(#inputEmail)');
await forceFill('#inputPassword', pass, 'pass(#inputPassword)');
let clicked = false;
for (const s of ['form:has(#inputPassword) button[type=submit]', 'button:has-text("Log In")', 'button:has-text("Login")', 'button:has-text("Sign In")']) {
const b = await page.$(s).catch(() => null);
if (b) { await b.click({ timeout: 6000 }).catch(() => {}); console.log('login submit via', s); clicked = true; break; }
}
if (!clicked) { await page.press('#inputPassword', 'Enter').catch(() => {}); console.log('submit via Enter'); }
await page.waitForTimeout(7000);
const info = await page.evaluate(() => {
const t = document.body ? document.body.innerText : '';
return { url: location.href, title: document.title, err: /invalid|incorrect|not found|not recognised|failed|error/i.test(t), loggedIn: /log ?out|log ?off|sign ?out|my account|basket|welcome back/i.test(t), sample: t.replace(/\s+/g, ' ').slice(0, 220) };
}).catch(() => ({ url: page.url(), title: '', err: false, loggedIn: false, sample: '(eval failed)' }));
console.log('post url:', info.url, '| title:', info.title.slice(0, 40), '| loggedIn~', info.loggedIn, '| err~', info.err);
console.log('sample:', info.sample);
if (info.loggedIn && !info.err) { await ctx.storageState({ path: path.join(process.cwd(), '.auth', 'osborne-state.json') }).catch(() => {}); console.log('✅ session saved -> .auth/osborne-state.json'); }
await page.screenshot({ path: 'probe-osborne.png' }).catch(() => {});
await page.waitForTimeout(1500);
await ctx.close();