You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
4.2 KiB
119 lines
4.2 KiB
import { randomUUID } from "node:crypto";
|
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { chromium } from "playwright";
|
|
|
|
const ENTRY =
|
|
"https://www.priority1.com/instant-freight-quotes/#New%20Step%201";
|
|
const CLICK = { noWaitAfter: true as const };
|
|
|
|
async function typeField(
|
|
page: import("playwright").Page,
|
|
sel: string,
|
|
value: string,
|
|
) {
|
|
const loc = page.locator(`#container-mh ${sel}`).first();
|
|
await loc.click(CLICK);
|
|
await loc.fill("");
|
|
await loc.pressSequentially(value, { delay: 20 });
|
|
}
|
|
|
|
async function main() {
|
|
const out = join(process.cwd(), ".rpa", "priority1-probe-quotes");
|
|
mkdirSync(out, { recursive: true });
|
|
const network: Array<{ url: string; status: number; body: string }> = [];
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const page = await browser.newPage();
|
|
page.on("response", async (res) => {
|
|
const url = res.url();
|
|
if (!/quote|rate|pricing|ltl|feathery|priority/i.test(url)) return;
|
|
if (!res.headers()["content-type"]?.includes("json")) return;
|
|
try {
|
|
const body = await res.text();
|
|
if (body.length > 50) {
|
|
network.push({ url, status: res.status(), body: body.slice(0, 8000) });
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
});
|
|
|
|
await page.goto(ENTRY, { waitUntil: "domcontentloaded" });
|
|
await page.locator("#container-mh").waitFor();
|
|
await page.waitForTimeout(1500);
|
|
await page.locator("#cb_st_ltl").click({ force: true, ...CLICK });
|
|
await page
|
|
.locator("#container-mh")
|
|
.getByText(/^Less Than Truckload/i)
|
|
.first()
|
|
.click(CLICK)
|
|
.catch(() => undefined);
|
|
await page.waitForTimeout(600);
|
|
await typeField(page, "#origin-zip", "10118");
|
|
await typeField(page, "#destination-zip", "90017");
|
|
await page.locator("#container-mh #freight-shipment").selectOption("One Time Shipment");
|
|
await typeField(page, "#pickup-date", "07/01/2026");
|
|
await typeField(page, "#business-email", "niqijiti542@gmail.com");
|
|
await typeField(page, "#client-phone", "2125550198");
|
|
|
|
const btn1 = page
|
|
.locator("#container-mh")
|
|
.getByRole("button", { name: /GET INSTANT QUOTES/i })
|
|
.first();
|
|
await btn1.click(CLICK);
|
|
await page.locator("#container-mh #length").waitFor({ state: "visible", timeout: 60_000 });
|
|
|
|
await typeField(page, "#length", "48");
|
|
await typeField(page, "#width", "40");
|
|
await typeField(page, "#height", "48");
|
|
await typeField(page, "#weight", "30");
|
|
await typeField(page, "#number-of-pallets", "2");
|
|
const pkg = page
|
|
.locator("#container-mh select")
|
|
.filter({ has: page.locator('option:has-text("Pallet")') });
|
|
if ((await pkg.count()) > 0) await pkg.first().selectOption("Pallet");
|
|
await page.locator("#container-mh #PickupLocation").selectOption("Residence / Home Business");
|
|
await page.locator("#container-mh #DropoffLocation").selectOption("Residence / Home Business");
|
|
|
|
const btn2 = page
|
|
.locator("#container-mh")
|
|
.getByRole("button", { name: /GET INSTANT QUOTES/i })
|
|
.first();
|
|
await btn2.click(CLICK);
|
|
|
|
const started = Date.now();
|
|
while (Date.now() - started < 90_000) {
|
|
const has = await page.evaluate(() => /\$[\d,]+\.\d{2}/.test(document.body.innerText));
|
|
if (has) break;
|
|
await page.waitForTimeout(2000);
|
|
}
|
|
|
|
const dom = await page.evaluate(() => {
|
|
const cards = [...document.querySelectorAll("#container-mh *")].filter((el) => {
|
|
const t = el.textContent || "";
|
|
return /\$[\d,]+\.\d{2}/.test(t) && t.length < 500;
|
|
});
|
|
return cards.slice(0, 15).map((el) => ({
|
|
tag: el.tagName,
|
|
class: (el as HTMLElement).className?.slice?.(0, 80),
|
|
text: el.textContent?.replace(/\s+/g, " ").trim().slice(0, 200),
|
|
}));
|
|
});
|
|
|
|
writeFileSync(join(out, "network.json"), JSON.stringify(network, null, 2));
|
|
writeFileSync(join(out, "dom.json"), JSON.stringify(dom, null, 2));
|
|
await page.screenshot({ path: join(out, "quote.png"), fullPage: true });
|
|
console.log("network:", network.length, "dom cards:", dom.length);
|
|
console.log("out:", out);
|
|
for (const n of network.slice(0, 5)) {
|
|
console.log("---", n.url, n.status);
|
|
console.log(n.body.slice(0, 500));
|
|
}
|
|
console.log("dom sample:", JSON.stringify(dom.slice(0, 5), null, 2));
|
|
|
|
await browser.close();
|
|
}
|
|
|
|
main().catch(console.error);
|