// /app/planbuild_fetch.js const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({ headless: true }); const ctx = await browser.newContext({ userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', locale: 'en-AU' }); const page = await ctx.newPage(); let printed = false; page.on('response', async (res) => { // capture the app’s own search response if/when it fires const u = res.url(); if (u.endsWith('/insecure/advertisementsearch/api/advertisements/search') && res.request().method() === 'POST') { try { const json = await res.json(); console.log(JSON.stringify({source: 'api', items: json})); printed = true; } catch (_) {} } }); await page.goto('https://portal.planbuild.tas.gov.au/insecure/advertisementsearch', { waitUntil: 'networkidle' }); // Accept modal if present try { await page.click('#contBtn', { timeout: 1500 }); } catch (_) {} // Give the app a moment to run its own search await page.waitForTimeout(2000); if (!printed) { // If the app didn’t auto-search, ask it to (uses CSRF + recaptcha internally) await page.evaluate(() => { const header = document.querySelector('meta[name="_csrf_header"]')?.content || 'X-CSRF-TOKEN'; const token = document.querySelector('meta[name="_csrf"]')?.content || ''; fetch('/insecure/advertisementsearch/api/advertisements/search', { method: 'POST', headers: { 'Content-Type': 'application/json', [header]: token }, body: JSON.stringify({ page: 0, size: 200, lgas: [] }) }).catch(()=>{}); }); await page.waitForTimeout(2500); } if (!printed) { // As a fallback, scrape the rendered rows (if the app injected them) const rows = await page.$$eval('.advertisement-result-row', nodes => nodes.map(n => ({ id: n.id || '', addressString: (n.querySelector('.col-xs-8')?.innerText || '').trim(), name: (n.querySelector('.col-xs-4')?.innerText || '').trim() })).filter(x => x.name && x.addressString) ); console.log(JSON.stringify({source: 'dom', items: rows})); } await browser.close(); })();