require("dotenv").config(); const path = require("node:path"); const fs = require("node:fs/promises"); const { chromium } = require("playwright"); const DEFAULT_URL = "https://bikegear.in/acerbis"; const TEST_URL = process.argv[2] || process.env.BIKEGEAR_PROXY_TEST_URL || DEFAULT_URL; const WARM_URL = process.env.BIKEGEAR_PROXY_TEST_WARM_URL || DEFAULT_URL; const PROFILE_PATH = path.resolve("data/sources/bikegear/.browser-profile-test"); const STEALTH_SCRIPT = ` (() => { Object.defineProperty(navigator, 'webdriver', { get: () => false }); const fakePlugin = (name, file, mimes) => { const p = Object.create(Plugin.prototype); Object.defineProperty(p, 'name', { get: () => name }); Object.defineProperty(p, 'filename', { get: () => file }); Object.defineProperty(p, 'length', { get: () => mimes.length }); return p; }; Object.defineProperty(navigator, 'plugins', { get: () => [ fakePlugin('Chrome PDF Plugin', 'internal-pdf-viewer', ['application/x-google-chrome-pdf']), fakePlugin('Chrome PDF Viewer', 'mhjfbmdgcfjbbpaeojofohoefgiehjai', ['application/pdf']), fakePlugin('Native Client', 'internal-nacl-plugin', ['application/x-nacl']), ], }); if (!window.chrome) window.chrome = { runtime: {}, loadTimes: () => {}, csi: () => {}, app: {} }; Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); })(); `; function getProxyUrl() { if (process.env.BIKEGEAR_PROXY_URL) { return process.env.BIKEGEAR_PROXY_URL; } const host = process.env.BIKEGEAR_PROXY_HOST; const port = process.env.BIKEGEAR_PROXY_PORT; if (!host || !port) { return null; } const protocol = String(process.env.BIKEGEAR_PROXY_PROTOCOL || "http").replace(/:$/, ""); const username = buildWebshareUsername(process.env.BIKEGEAR_PROXY_USERNAME || ""); const password = process.env.BIKEGEAR_PROXY_PASSWORD || ""; const auth = username ? `${encodeURIComponent(username)}:${encodeURIComponent(password)}@` : ""; return `${protocol}://${auth}${host}:${port}`; } function buildWebshareUsername(baseUsername) { const username = String(baseUsername || "").trim(); if (!username) return ""; const country = String(process.env.BIKEGEAR_PROXY_COUNTRY || "").trim().toLowerCase(); const sessionId = String(process.env.BIKEGEAR_PROXY_SESSION_ID || "").trim(); if (!country && !sessionId) return username; const countryPart = country && !new RegExp(`-${country}(?:-|$)`, "i").test(username) ? `-${country}` : ""; const sessionPart = sessionId && !new RegExp(`-${sessionId}$`).test(username) ? `-${sessionId}` : ""; return `${username}${countryPart}${sessionPart}`; } function maskProxyUrl(proxyUrl) { if (!proxyUrl) return "none"; try { const u = new URL(proxyUrl); return `${u.protocol}//${u.username ? "@" : ""}${u.host}`; } catch { return ""; } } function buildProxyOptions(proxyUrl) { if (!proxyUrl) return {}; const u = new URL(proxyUrl); return { proxy: { server: `${u.protocol}//${u.host}`, ...(u.username ? { username: decodeURIComponent(u.username), password: decodeURIComponent(u.password), } : {}), }, }; } async function main() { const proxyUrl = getProxyUrl(); await fs.mkdir(PROFILE_PATH, { recursive: true }); console.log(`[BIKEGEAR-PROXY-TEST] url=${TEST_URL}`); console.log(`[BIKEGEAR-PROXY-TEST] warmUrl=${TEST_URL === WARM_URL ? "-" : WARM_URL}`); console.log(`[BIKEGEAR-PROXY-TEST] proxy=${maskProxyUrl(proxyUrl)}`); const context = await chromium.launchPersistentContext(PROFILE_PATH, { headless: true, args: [ "--no-sandbox", "--disable-setuid-sandbox", "--disable-blink-features=AutomationControlled", ], userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", locale: "en-US", viewport: { width: 1280, height: 800 }, extraHTTPHeaders: { "Accept-Language": "en-US,en;q=0.9" }, ...buildProxyOptions(proxyUrl), }); try { await context.addInitScript(STEALTH_SCRIPT); const page = await context.newPage(); if (TEST_URL !== WARM_URL) { const warmResponse = await page.goto(WARM_URL, { waitUntil: "domcontentloaded", timeout: 45000 }); await page.waitForTimeout(8000); console.log(`[BIKEGEAR-PROXY-TEST] warmStatus=${warmResponse?.status() || 0}`); } let response; if (TEST_URL !== WARM_URL && !TEST_URL.includes("?page=")) { const clickResult = await clickLinkForUrl(page, TEST_URL); response = clickResult.response; console.log(`[BIKEGEAR-PROXY-TEST] clickedLink=${clickResult.clicked}`); } if (!response) { response = await page.goto(TEST_URL, { waitUntil: "domcontentloaded", timeout: 45000, ...(TEST_URL !== WARM_URL ? { referer: WARM_URL } : {}), }); } await page.waitForTimeout(8000); const status = response?.status() || 0; const title = await page.title().catch(() => ""); const html = await page.content(); const productLinks = (html.match(/class="product-img/g) || []).length; const hasChallengeTitle = /just a moment|attention required|access denied/i.test(title); const hasChallengeMarkup = /cf-error|cf-browser-verification/i.test(html); const hasCloudflare = hasChallengeTitle || hasChallengeMarkup; console.log(`[BIKEGEAR-PROXY-TEST] status=${status}`); console.log(`[BIKEGEAR-PROXY-TEST] title=${title || "-"}`); console.log(`[BIKEGEAR-PROXY-TEST] productLinks=${productLinks}`); console.log(`[BIKEGEAR-PROXY-TEST] cloudflarePage=${hasCloudflare}`); if (status === 200 && productLinks > 0 && !hasChallengeTitle) { console.log("[BIKEGEAR-PROXY-TEST] PASS browser can read BikeGear listing through this proxy."); return; } process.exitCode = 1; console.error("[BIKEGEAR-PROXY-TEST] FAIL proxy/browser did not reach a usable BikeGear listing."); } finally { await context.close().catch(() => {}); } } async function clickLinkForUrl(page, url) { const locator = page.locator(`xpath=//a[@href=${xpathString(url)}]`).first(); const count = await locator.count().catch(() => 0); if (!count) return { clicked: false, response: null }; const navigationPromise = page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 45000 }) .catch(() => null); await locator.scrollIntoViewIfNeeded().catch(() => {}); await locator.hover({ timeout: 5000 }).catch(() => {}); await page.waitForTimeout(500); await locator.click({ timeout: 10000 }).catch(() => null); await page.waitForURL(url, { timeout: 45000 }).catch(() => {}); return { clicked: true, response: await navigationPromise, }; } function xpathString(value) { const text = String(value); if (!text.includes("'")) return `'${text}'`; if (!text.includes('"')) return `"${text}"`; return `concat('${text.replace(/'/g, `', "'", '`)}')`; } main().catch((error) => { process.exitCode = 1; console.error(`[BIKEGEAR-PROXY-TEST] ERROR ${error.message}`); });