From 9c8643086a36d394ea5fff8b8d7424921e508a73 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Thu, 23 Apr 2026 10:43:38 -0400 Subject: [PATCH] fix: refine scraper output behavior --- packages/core/src/scrapers/ebay.ts | 6 +++ packages/core/src/scrapers/facebook.ts | 14 ++--- packages/core/test/ebay-core.test.ts | 62 ++++++++++++++++++++++ packages/core/test/facebook-core.test.ts | 66 ++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 7 deletions(-) diff --git a/packages/core/src/scrapers/ebay.ts b/packages/core/src/scrapers/ebay.ts index a7ef028..d579d6c 100644 --- a/packages/core/src/scrapers/ebay.ts +++ b/packages/core/src/scrapers/ebay.ts @@ -87,6 +87,12 @@ function parseEbayPrice( cleaned.toUpperCase().includes("US $") ) { currency = "USD"; + } else if (cleaned.includes("£")) { + currency = "GBP"; + } else if (cleaned.includes("€")) { + currency = "EUR"; + } else if (cleaned.includes("¥")) { + currency = "JPY"; } return { cents, currency }; diff --git a/packages/core/src/scrapers/facebook.ts b/packages/core/src/scrapers/facebook.ts index e47f0c5..2178c68 100644 --- a/packages/core/src/scrapers/facebook.ts +++ b/packages/core/src/scrapers/facebook.ts @@ -1180,13 +1180,13 @@ export default async function fetchFacebookItems( console.log(`\nFound ${ads.length} raw ads. Processing...`); - const progressBar = new cliProgress.SingleBar( - {}, - cliProgress.Presets.shades_classic, - ); + const isTTY = process.stdout?.isTTY ?? false; + const progressBar = isTTY + ? new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic) + : null; const totalProgress = ads.length; const currentProgress = 0; - progressBar.start(totalProgress, currentProgress); + progressBar?.start(totalProgress, currentProgress); const items = parseFacebookAds(ads); @@ -1196,8 +1196,8 @@ export default async function fetchFacebookItems( typeof item.listingPrice?.cents === "number" && item.listingPrice.cents >= 0, ); - progressBar.update(totalProgress); - progressBar.stop(); + progressBar?.update(totalProgress); + progressBar?.stop(); console.log(`\nParsed ${pricedItems.length} Facebook marketplace listings.`); return finalizeResults(pricedItems); diff --git a/packages/core/test/ebay-core.test.ts b/packages/core/test/ebay-core.test.ts index 27eae06..b8b59d8 100644 --- a/packages/core/test/ebay-core.test.ts +++ b/packages/core/test/ebay-core.test.ts @@ -234,6 +234,68 @@ describe("eBay Scraper Cookie Handling", () => { ]); }); + test("maps pound prices to GBP", async () => { + global.fetch = mock(() => + Promise.resolve({ + ok: true, + text: () => + Promise.resolve(` + +
  • + +

    Stable Laptop Bundle

    + £123.45 +
  • + + `), + }), + ) as typeof fetch; + + const results = await fetchEbayItems("laptop", 1000); + + expect(results).toEqual([ + expect.objectContaining({ + listingPrice: expect.objectContaining({ currency: "GBP", cents: 12345 }), + }), + ]); + }); + + test("maps euro and yen prices to the matching currency labels", async () => { + global.fetch = mock(() => + Promise.resolve({ + ok: true, + text: () => + Promise.resolve(` + +
  • + +

    Euro Bundle

    + €123.45 +
  • +
  • + +

    Yen Bundle

    + ¥123 +
  • + + `), + }), + ) as typeof fetch; + + const results = await fetchEbayItems("bundle", 1000, { + keywords: ["bundle"], + }); + + expect(results).toEqual([ + expect.objectContaining({ + listingPrice: expect.objectContaining({ currency: "EUR", cents: 12345 }), + }), + expect.objectContaining({ + listingPrice: expect.objectContaining({ currency: "JPY", cents: 12300 }), + }), + ]); + }); + test("prefers the discounted Canadian-formatted price", async () => { global.fetch = mock(() => Promise.resolve({ diff --git a/packages/core/test/facebook-core.test.ts b/packages/core/test/facebook-core.test.ts index a9b126e..2a82e7b 100644 --- a/packages/core/test/facebook-core.test.ts +++ b/packages/core/test/facebook-core.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; +import cliProgress from "cli-progress"; import { classifyFacebookResponse, type FacebookListingDetails, @@ -16,6 +17,8 @@ import type { UnstableListingBuckets } from "../src/types/common"; import { formatCookiesForHeader } from "../src/utils/cookies"; import { formatCentsToCurrency } from "../src/utils/format"; +const originalStdoutIsTTY = process.stdout.isTTY; + type Assert = T; type IsExact = (() => G extends T ? 1 : 2) extends () => G extends U ? 1 : 2 @@ -50,6 +53,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { afterEach(() => { global.fetch = originalFetch; + process.stdout.isTTY = originalStdoutIsTTY; }); describe("Cookie Parsing", () => { @@ -621,6 +625,68 @@ describe("Facebook Marketplace Scraper Core Tests", () => { ]); }); + test("does not start a progress bar when stdout is not a TTY", async () => { + const mockSearchHtml = ``; + + process.stdout.isTTY = false; + const startSpy = mock(() => {}); + const updateSpy = mock(() => {}); + const stopSpy = mock(() => {}); + const originalStart = cliProgress.SingleBar.prototype.start; + const originalUpdate = cliProgress.SingleBar.prototype.update; + const originalStop = cliProgress.SingleBar.prototype.stop; + try { + cliProgress.SingleBar.prototype.start = startSpy; + cliProgress.SingleBar.prototype.update = updateSpy; + cliProgress.SingleBar.prototype.stop = stopSpy; + + global.fetch = mock(() => + Promise.resolve({ + ok: true, + text: () => Promise.resolve(mockSearchHtml), + url: "https://www.facebook.com/marketplace/toronto/search?query=chair", + headers: { + get: () => null, + }, + }), + ); + + const results = await fetchFacebookItems("chair", 1, "toronto", 25); + + expect(results).toHaveLength(1); + expect(startSpy).not.toHaveBeenCalled(); + expect(updateSpy).not.toHaveBeenCalled(); + expect(stopSpy).not.toHaveBeenCalled(); + } finally { + cliProgress.SingleBar.prototype.start = originalStart; + cliProgress.SingleBar.prototype.update = originalUpdate; + cliProgress.SingleBar.prototype.stop = originalStop; + } + }); + test("returns results and unstableResults when unstable mode is enabled", async () => { const mockSearchHtml = `