This repository contains the source code for a Node.js scraper that automates browser-based interaction with Amazon QuickSight dashboards: moving between dashboards and sheets, reading and manipulating controls (filters), extracting data from visuals, and downloading it to local CSV/PDF files.
This is not a finished application or a closed product. It's a codebase meant for development teams to build on top of — an API, a CLI, an Electron service, or whatever fits your stack — to programmatically "drive" QuickSight dashboards.
The use case behind this repo: letting an AI agent (for example, Claude Cowork, or any agent capable of executing tools) query existing QuickSight dashboards without needing to build and maintain a dedicated MCP server. Instead, your agent can be trained with a SKILL.md file describing how to navigate your dashboards, move controls, and extract data that is passed directly to the agent's local folder.
The advantage of this approach over an MCP that talks directly to the database:
- Your dashboards already have data consistency. The data QuickSight exposes is consistent with the actual operation of the business. The agent doesn't need to do text-to-SQL or understand the schema or the complexity of a relational database — it consumes the same analytics layer decision-makers already use.
- Speed. QuickSight datasets backed by SPICE respond to filter changes almost instantly, well beyond what an equivalent query against an RDBMS would deliver.
- Inherited security. Row-level security and permissions are already managed by the QuickSight user the agent operates as. The agent can only see what business rules already allow that user to see — there's no additional security surface to design.
What this codebase covers:
- Authentication: automated login against QuickSight, respecting the existing user/role and its permissions.
- Navigation: moving between dashboards, sheets, and visuals within a dashboard.
- Filter control: reading the current state of controls, applying new values, resetting to defaults, and saving filter configurations.
- Reading visuals: extracting the underlying data of a visual (tables, charts, KPIs) as shown after the current filters are applied.
- Export: downloading the extracted data to local CSV/PDF files, ready to be consumed by another process — for example, an AI agent.
Explicitly out of scope for this repo (left to each team's implementation):
- Exposing a formal API or CLI server.
- AI agent orchestration, SKILL.md or business logic.
- Packaging as a desktop application (Electron or otherwise).
This repository is the automation core of the scraper. It sits on top of the low-level browser layer, @factorbi/scraper-quicksight-browser (a thin Playwright wrapper it depends on), and turns a raw BrowserManager page into QuickSight-aware operations.
The public API is organized around one class per concern, plus the shared CSS/automation selectors:
| Export | Responsibility |
|---|---|
QuickSightAuth |
Two-step login/logout, session tracking, and cookie-based session restore. |
DashboardListScraperFromHomePage |
List the dashboards visible to the logged-in user from the home page. |
DashboardScraper |
Navigate to a dashboard and extract metadata, visuals, tables, and charts. |
SheetNavigator / SheetListFromDashboardPage |
List sheets and navigate between them by URL. |
FilterManager |
Read/apply/reset filters through the panel UI, and save/load filter configurations to disk. |
ParameterManager |
Apply filters by driving the QuickSight control panel (dates, dropdowns, multiselect, search panels), plus date helpers. |
CsvExporter |
Download a single visual's data as a CSV file. |
PdfExporter / pdfJobStore |
Generate a dashboard PDF synchronously or as a tracked async job. |
SELECTORS / getSelector / getAllSelectors |
Centralized QuickSight DOM/automation selectors. |
SheetIndexNotFoundError |
Typed error thrown when a requested sheet cannot be resolved. |
Every class takes a BrowserManager in its constructor, so they all share one browser context.
- Node.js >= 24
- pnpm (the repo pins
pnpm@11.10.0viapackageManager) - Chromium for Playwright (installed on first use with
pnpm exec playwright install chromium)
pnpm add @factorbi/scraper-quicksight-core @factorbi/scraper-quicksight-browser playwright
pnpm exec playwright install chromium@factorbi/scraper-quicksight-browser is a peer of this package — install it alongside so the two share the same Playwright version.
import { BrowserManager } from '@factorbi/scraper-quicksight-browser';
import {
QuickSightAuth,
DashboardScraper,
SheetNavigator,
ParameterManager,
CsvExporter,
} from '@factorbi/scraper-quicksight-core';
// 1. One browser context shared by every module
const browserManager = new BrowserManager({
headless: true,
userDataDir: './.browser-data', // persist the session between runs
});
await browserManager.initialize();
// 2. Log in
const auth = new QuickSightAuth(browserManager);
await auth.login({
quicksightUrl: process.env.QUICKSIGHT_URL!,
username: process.env.QUICKSIGHT_USERNAME!,
password: process.env.QUICKSIGHT_PASSWORD!,
});
// 3. Scrape a dashboard
const scraper = new DashboardScraper(browserManager);
await scraper.navigateToDashboard('https://<account>.quicksight.aws.amazon.com/sn/dashboards/<id>');
const data = await scraper.scrapeDashboard('<id>', { extractTables: true, extractCharts: true });
// 4. Change a filter, then re-read
const params = new ParameterManager(browserManager);
await params.setParameters({ p_fecha_inicio_actual: '2026-01-01', p_fecha_fin_actual: '2026-06-30' });
const updated = await scraper.scrapeDashboard('<id>', { extractTables: true });
// 5. Navigate sheets and export a visual
const sheets = new SheetNavigator(browserManager);
const list = await sheets.getSheets();
await sheets.navigateToSheet(list[1].url);
const csv = new CsvExporter(browserManager);
const result = await csv.exportVisualCsv('Ventas por sucursal');
console.log(`CSV saved to ${result.filePath}`);
await browserManager.close();| Method | Description |
|---|---|
login(credentials, sessionId?) |
Navigate to QuickSight and perform the two-step (or single-page) login. Returns a Session; skips login if already authenticated. |
logout() |
Sign out via the user menu (no-op if not logged in or already on a login page). |
tryRestoreSession(quicksightUrl, sessionId) |
Verify a persisted cookie session is still valid; returns true if no re-login is needed. |
getSession() / isAuthenticated() |
Current Session or auth state. |
updateSessionActivity() |
Bump the session's lastActivity timestamp. |
getBrowserManager() |
The BrowserManager this instance drives. |
| Method | Description |
|---|---|
scrape(dashboardsUrl) |
Return the DashboardListItem[] visible on the QuickSight home/dashboards page. |
| Method | Description |
|---|---|
navigateToDashboard(dashboardUrl) |
Open a dashboard and wait for it to be ready. |
scrapeDashboard(dashboardId, options?) |
Extract a DashboardData snapshot. options (ScraperDashboardOptions) toggles metadata/visuals/tables/charts/rawText/screenshot extraction. |
extractMetadata(page, dashboardId) |
Title/description metadata for the current dashboard. |
extractVisuals(page) |
List the visuals on the current sheet. |
extractTables(page, targetTable?) |
Extract table data (optionally a single named table). |
extractCharts(page) |
Extract chart series/data points. |
scrollToVisual(rawTitle) |
Scroll a specific visual into view. |
takeVisualScreenshot(visualIndex) |
Base64 screenshot of one visual, or null. |
| Method | Description |
|---|---|
SheetNavigator.getSheets() |
List sheets (Sheet[]) on the current dashboard. |
SheetNavigator.navigateToSheet(sheetUrl) |
Navigate to a sheet by its URL and return the now-current Sheet. |
SheetListFromDashboardPage.scrape(dashboardUrl) |
Open a dashboard and return its sheets. |
SheetListFromDashboardPage.extractSheets(page) |
Extract sheets (including sheetId parsed from page metadata) from an already-loaded page. |
Applies filters by driving the QuickSight control panel UI (never by URL navigation), so it handles date pickers, native selects, custom comboboxes, large VegaAutocomplete multiselects, and search-panel controls.
| Method | Description |
|---|---|
setParameters(parameters, reason?, options?) |
Apply a ParameterConfiguration through the panel. options.skipReset avoids the reset step when the current state is known. Returns a SetParametersResult. |
resetParameters() |
Reset all controls to their factory defaults. |
getCurrentParameters() |
Read the current parameters from the URL. |
getParameterSchema() |
Map of supported p_* parameter keys to human descriptions. |
validateDateFormat(dateString) |
Validate a YYYY-MM-DD string. |
formatDate(date) |
Date → YYYY-MM-DD. |
calculateDateRange(period) |
Resolve a named period (ytd, this_month, q1, last_year, …) to { start, end }. |
| Method | Description |
|---|---|
getFilters() |
Read the current Filter[] state from the control panel. |
applyFilters(request) |
Apply a FilterApplyRequest and return the updated filters. |
resetFilters() |
Clear all filters (reset button, or per-filter fallback). |
saveFilterConfiguration(dashboardId) |
Persist the current filters to a JSON file under EXPORT_DIR; returns the path. |
loadFilterConfiguration(filePath) |
Load a saved JSON config into a FilterApplyRequest. |
extractAllFilters(page) |
Extract filters from an already-loaded page. |
| Method | Description |
|---|---|
exportVisualCsv(visualTitle, outputDir?, timeoutMs?) |
Download one visual's data as CSV. Returns a CsvResult (headers, rows, rowCount, filePath, downloadUrl). Defaults: outputDir = EXPORT_DIR ?? './exports', timeoutMs = 60000. |
| Method | Description |
|---|---|
generatePdf(outputDir?, timeoutMs?) |
Generate the dashboard PDF and resolve with { filePath, filename, downloadUrl }. Defaults: outputDir = EXPORT_DIR ?? './exports', timeoutMs = 300000. |
generatePdfAsync(outputDir?, timeoutMs?) |
Start generation in the background and return a PdfJob immediately. |
pdfJobStore.get(id) / create() / update(id, patch) |
In-memory PDF job registry. |
pdfJobStore.waitForCompletion(id, timeoutMs?) |
Await a job until it is ready/error (default timeout 300000 ms). |
| Export | Description |
|---|---|
SELECTORS |
Centralized QuickSight selectors grouped by area (login, dashboard, visuals, filters, dashboardList, sheets, navigation, modal, tooltip, general). |
getSelector(category, element) |
Look up a single selector string. |
getAllSelectors(category) |
All selectors for a category. |
Always update selectors here rather than hardcoding them elsewhere — QuickSight's DOM changes over time and this is the single place to keep them in sync.
Shared TypeScript types (Sheet, DashboardData, Filter, ParameterConfiguration, CsvResult, PdfJob, Session, LoginCredentials, …) are re-exported from the package subpath:
import type { DashboardData, ParameterConfiguration } from '@factorbi/scraper-quicksight-core/types';EXPORT_DIR— output directory for CSV/PDF/filter-config files (defaults to./exports).
Credentials and the QuickSight URL are passed explicitly to QuickSightAuth.login(), so how you source them (env vars, a secrets manager, a settings table) is up to your integration.
pnpm install
pnpm lint # eslint --fix
pnpm lint:check # eslint (no fixes)
pnpm typecheck # tsc --noEmit
pnpm build # bundle with tsdownMPL-2.0 — © FactorBI. See NOTICE.
This approach — reading existing QuickSight dashboards instead of generating dynamic SQL against the database — is the foundation of some of the AI-powered BI advisory solutions we've developed at Factor BI.