This guide walks through everything needed to stand up the InvestmentsView stack against the live Questrade APIs and capture a screenshot of a specific account view (for example the RESP account). Follow each section carefully to avoid the most common authentication and CORS pitfalls.
- Node.js 20.19+ (matches the versions used by the Vite frontend and the Express proxy).
- npm (ships with Node.js).
- Valid Questrade refresh token for every login you plan to mirror. Obtain these from the Questrade portal and keep them secret.
- Network access to Questrade. The proxy must be able to reach
https://login.questrade.comand the REST endpoints underhttps://api.questrade.com. - Git ignored secrets files:
server/.env– copy from.env.exampleand adjustCLIENT_ORIGINorPORTonly if necessary.server/token-store.json– contains refresh tokens; never commit this file.server/accounts.json– define friendly names (required when you need the RESP alias in the UI).
Do not commit
server/.env,server/token-store.json, or any other file containing credentials. They are already listed in.gitignore– keep it that way.
For repeat runs you can automate most of the setup with
scripts/run-live-stack.sh. The helper script installs dependencies (unless
you opt out), writes server/.env when it is missing, seeds refresh tokens,
and launches both the Express proxy and Vite dev server in the foreground. It
also supports optional Playwright-based screenshots once the UI is ready.
# Example – provide secrets via environment variables to avoid shell history
FRED_API_KEY=... \
REFRESH_TOKEN=... \
scripts/run-live-stack.sh --screenshot resp.pngKey flags:
--fred-key,--client-origin, and environment variables configure the generatedserver/.envwhen it does not yet exist.--refresh-token(orREFRESH_TOKEN) pipes the supplied value throughnpm run seed-token, guaranteeing that the stored refresh token is always the latest rotation from Questrade. After each successful run copy the new value fromserver/token-store.jsonand use it the next time you invoke the helper. Re-using the pre-rotation token will immediately trigger anHTTP 400during the next seed attempt.--skip-install,--no-backend, and--no-frontendlet you reuse already running services.--screenshot <path>captures a full-page Playwright screenshot after both servers respond. On the first run the helper installs the Playwright browser binaries and any missing Ubuntu libraries, which can take a few minutes while Chromium downloads. The script records the cache location and skips the heavy install on later runs, retrying with a fresh install only if the initial screenshot attempt fails.
Typical runtime expectations once the caches are warm:
- ~90 seconds for the backend to finish the historical sync against
Questrade. During this window the
/api/summaryrequests log a large number of "Using cached access token" lines—this is normal while the initial sync is replayed. - ~15–20 seconds for Playwright to launch Chromium and take the full-page screenshot when the browser binaries are already cached. First runs take longer because the Chromium build must be downloaded.
The script exits only after you press Ctrl+C, ensuring both dev servers shut
down cleanly and any rotated refresh token is flushed to server/token-store.json.
The helper seeds refresh tokens using the same undici-based client as the backend proxy. If the exchange fails with
HTTP 400the supplied refresh token is no longer valid—request a fresh token from Questrade before retrying.The helper also refuses to start when port
4000or the configured frontend port is already taken. Stop any stalenpm run devinstances (or pass a custom--client-origin) before rerunning so the screenshot automation does not point at an old server.
-
Create
server/.envwith the contents (substitute your real FRED API key for the placeholder):CLIENT_ORIGIN=http://localhost:5173 PORT=4000 FRED_API_KEY=<FRED_API_KEY>
-
Seed the refresh token into
server/token-store.json. You can still use the helper script, but if you need to paste the file manually start with the exact structure below. Replace<REFRESH_TOKEN>with the real refresh token that you obtain from Questrade and keep it private.{ "logins": [ { "id": "daniel", "label": "daniel.bigham@gmail.com", "email": "daniel.bigham@gmail.com", "refreshToken": "<REFRESH_TOKEN>", "updatedAt": "2025-10-12T00:55:10.810Z" } ], "updatedAt": "2025-10-12T00:55:10.810Z" }To seed via script instead of manual editing:
cd server npm install npm run seed-token -- "<refreshTokenFromQuestrade>" --id=daniel --label="daniel.bigham@gmail.com" --email="daniel.bigham@gmail.com"
The script exchanges the refresh token, persists the rotated token that Questrade returns, and preserves any other stored logins.
-
Populate
server/accounts.jsonwith an entry for the RESP account using the exact template below:{ "accounts": [ { "number": "53540936", "name": "RESP", "portalAccountId": "95094100-0516-40b2-0cff-0a584b8c9f19", "cagrStartDate": "2025-09-22", "ignoreSittingCash": 200, "default": true } ] }Keep any additional accounts you need; the proxy watches this file for updates.
-
Return to the repository root before proceeding:
cd ..
-
From the repository root, launch the backend in dev mode:
cd server npm run dev -
Wait for the console to log that it is listening on port 4000. On the first frontend request the proxy will:
- Refresh the access token using the stored refresh token.
- Log the
api_serverURL supplied by Questrade. - Persist a new refresh token to
server/token-store.json.
Keep the terminal open – the server must stay running while you capture the screenshot.
-
After the first successful refresh, open
server/token-store.jsonand note the new refresh token. Use this latest value for any subsequent runs so you do not receive HTTP 400 errors from Questrade.
-
Open a second terminal.
-
Install dependencies and start Vite:
cd client npm install npm run dev -- --hostThe
--hostflag binds to0.0.0.0, which is required when tooling accesses the site throughlocalhostinside a container. -
Vite prints the development server URLs. Use the exact value in
CLIENT_ORIGIN(for examplehttp://localhost:5173/). Browsing with a different host such ashttp://127.0.0.1:5173/will fail CORS checks and the UI will display "Failed to fetch". -
If you are driving the UI from another container (e.g., Playwright in
browser_container) forward both ports 5173 and 4000. The frontend talks to the Express proxy athttp://localhost:4000. If that port is not exposed to the browser container the account list never loads, the UI shows empty tables, and Playwright eventually times out waiting for selectors.
- Open the frontend in a real browser (manual or automated) using the origin from the previous step.
- The UI will immediately fetch
/api/summary. Confirm in the backend logs that the request succeeds (HTTP 200). If it fails with HTTP 400, re-run the seed script with the refresh token currently stored inserver/token-store.json. - Once the data loads, use the account selector in the top-left corner to choose the RESP account. The metrics, holdings table, and cash breakdown should now reflect only that account.
- Wait patiently for all metrics to populate. The holdings table shows a spinner while data is loading; the spinner disappears when the API response has been processed. Depending on API latency, this can take 30 seconds or longer, so avoid stopping the servers prematurely.
- The first
/api/summaryrequest performs a full historical sync (multiple years of monthly activity per account). It is normal for this call to run for several minutes and flood the proxy logs with Questrade requests. Do not interrupt it or assume it has hung—even a simplecurlof the endpoint will block until the sync finishes.
-
Manual browser: In Chromium-based browsers press
Ctrl+Shift+P/Cmd+Shift+P, run theCapture full size screenshotcommand, and save the image. Ensure the account selector displaysRESPin the header before capturing. -
Playwright: With the frontend already running, execute:
npx playwright screenshot --device="Desktop Chrome" --output=resp.png http://localhost:5173/Then crop as needed to focus on the account view.
When the test runner executes in a different sandbox (such as a dedicated browser container), make sure port forwarding is set up before invoking Playwright:
# Example when using the eval harness container.exec(port_forward=[5173, 4000])Forgetting to forward port 4000 causes the frontend API calls to hang forever because the browser cannot reach the proxy.
-
Other automation: Any headless driver is acceptable as long as it loads the same origin and waits for the React app to finish rendering before snapping the image.
Store screenshots under a temporary artifacts directory so they are not committed to the repository.
| Symptom | Fix |
|---|---|
Failed to fetch toast in the UI |
Make sure the browser is visiting the exact origin defined in CLIENT_ORIGIN (typically http://localhost:5173/). |
Backend logs 400 Bad Request from Questrade |
Your refresh token is stale. Copy the newest token from server/token-store.json (it is rotated on every refresh) and re-run npm run seed-token -- "<token>" --id=<loginId>. |
| Backend cannot reach Questrade (ENOTFOUND / ECONNREFUSED) | Verify internet access from the environment and retry. Questrade endpoints must be reachable. |
| Screenshot shows loading spinners | Wait for the /api/summary request to finish (check the Network tab or server logs) before capturing. |
| Accidentally committed secrets | Reset the commit immediately and rotate the affected refresh tokens in the Questrade portal. |
- Stop the frontend (
Ctrl+C). - Stop the backend (
Ctrl+C). - Securely store the latest refresh token (the value in
server/token-store.json). You will need it the next time you run the stack. - Delete any temporary screenshots that contain sensitive financial information when they are no longer needed.
Following the above sequence allows an LLM (or any operator) to consistently spin up the project, authenticate with live Questrade APIs, and capture accurate RESP account screenshots without running into expired refresh tokens or CORS blockers.