Skip to content

refactor(core): decompose Maestro screenshot relay out of api.js#2286

Open
Sriram567 wants to merge 8 commits into
masterfrom
refactor/maestro-screenshot-decomposition
Open

refactor(core): decompose Maestro screenshot relay out of api.js#2286
Sriram567 wants to merge 8 commits into
masterfrom
refactor/maestro-screenshot-decomposition

Conversation

@Sriram567

Copy link
Copy Markdown
Contributor

Summary

PR #2217 (cross-platform Maestro view-hierarchy resolver + screenshot filePath relay) shipped its resolver cleanly in maestro-hierarchy.js, but landed ~639 lines of relay glue directly in packages/core/src/api.js — a ~440-line /percy/maestro-screenshot route closure plus the handleComparisonUpload multipart handler and the parsePngDimensions/manualScreenshotWalk helpers. api.js is meant to be a route table that delegates to domain modules (the handleSyncJob-in-snapshot.js pattern); this restores that boundary.

Behavior-preserving refactor — no wire/runtime change. Decomposes the relay into four cohesive modules and reduces each route to a one-line delegate:

New module Responsibility
comparison-upload.js /percy/comparison/upload multipart handler
maestro-screenshot.js /percy/maestro-screenshot orchestrator + request validation + parsePngDimensions + payload build + sync/async upload
maestro-screenshot-file.js screenshot location (glob, manualScreenshotWalk, mtime pick) + realpath/session-root security check
maestro-regions.js region input validation + element/coordinate → comparison-payload resolution

encodeURLSearchParams moved to utils.js (next to the other URL helpers) so the modules share it without a circular import.

api.js: 994 → 382 lines.

Commits (one cohesive unit each)

  1. Extract comparison-upload.js
  2. Relocate the maestro-screenshot handler wholesale (verbatim move; green checkpoint)
  3. Extract maestro-screenshot-file.js (file location + security)
  4. Extract maestro-regions.js (region validation + resolution)
  5. Unit specs for the extracted modules + drop the now-dead ServerError import

Invariants preserved

  • The async-generator sync-drain IIFE (and its comment) — carried verbatim; re-simplifying it reintroduces a silent production hang.
  • cachedDump stays request-scoped (call-local inside resolveRegions) — no cross-request hierarchy leak.
  • parsePngDimensions fill-not-override semantics; strict sync === true; three independent region output fields; algorithm pass-through (no relay-side enum validation); percy.grpcClientCache threading.
  • Every /* istanbul ignore */ annotation travels attached to its exact statement.
  • The .semgrepignore path-traversal suppression moved from api.js to maestro-screenshot-file.js (the new owner of the path.join sinks); the api.js entry is dropped (it predated this code and has no remaining tainted joins).

Testing

  • api.test.js (the HTTP-level behavior contract, incl. the non-mocked sync-drain canary and the locateScreenshot filePath/glob/traversal specs) passes unchanged after every unit — the single remaining failure is the pre-existing IPv4/IPv6 ECONNREFUSEDAggregateError flake (api.test.js:655), unrelated to this change.
  • 19 new test/unit/ specs (parsePngDimensions, validateRegionInputs, resolveRegions coordinate paths) pass.
  • Coverage: the verbatim move keeps every previously-covered line on the same execution path (api.test.js drives the same route into the relocated code) with ignores intact, plus direct unit coverage of the pure functions. The 100% NYC gate and semgrep are validated authoritatively in CI (the full local suite has ~27 unrelated environmental flakes — browser-launch timeouts, port-5338 collisions, system-proxy detection — that make a local full-coverage run unreliable on this machine).

Post-Deploy Monitoring & Validation

  • No additional operational monitoring required: behavior-preserving internal decomposition — no HTTP contract, resolver-cascade, drift-envelope, SDK, or runtime change. The deployed artifact behaves identically.
  • CI gates to watch on this PR: full Jasmine suite, nyc --check-coverage (100% branches/lines/funcs/statements), and the semgrep path-join-resolve-traversal job (confirms the .semgrepignore retarget to maestro-screenshot-file.js).

🤖 Generated with Claude Code

…son-upload.js

Move handleComparisonUpload out of api.js into its own module, mirroring the
handleSyncJob extraction pattern. Promote the shared encodeURLSearchParams
helper to utils.js (alongside the other URL helpers) so both api.js and the
new module can use it without a circular import. No behavior change.
…ro-screenshot.js

Move the ~440-line route handler body, plus the parsePngDimensions and
manualScreenshotWalk helpers, verbatim out of api.js into a dedicated
maestro-screenshot.js module exporting handleMaestroScreenshot(req, res, percy).
api.js now declares the route as a one-line delegate, mirroring the
handleComparisonUpload/handleSyncJob pattern. All istanbul-ignore annotations
travel verbatim with their statements. Move the file-level semgrep
path-traversal suppression from api.js to maestro-screenshot.js (the new owner
of the path.join sinks). No behavior change.

api.js drops from 994 to 383 lines.
…creenshot-file.js

Carve the file-location concern (platform glob pattern, manualScreenshotWalk
fallback, multi-match mtime selection, realpath + session-root prefix check)
out of handleMaestroScreenshot into locateScreenshot(). The handler now passes
the shape-validated filePath and gets back a canonicalized absolute path or a
404. Move the semgrep path-traversal suppression to the new module (it now
owns the path.join sinks). istanbul-ignore annotations travel verbatim.
No behavior change.
…egions.js

Move validateRegionInputs (shape checks) and resolveRegions (element/coordinate
bbox resolution → comparison-payload fragments) out of handleMaestroScreenshot
into a dedicated module. The hierarchy dump and warn-once flag stay
request-scoped (call-local inside resolveRegions), preserving the per-request
memoization invariant. Three independent output fields, algorithm pass-through,
and percy.grpcClientCache threading are unchanged. istanbul-ignore annotations
travel verbatim. No behavior change.

maestro-screenshot.js is now a 182-line orchestrator.
…import

Add test/unit/maestro-screenshot.test.js (parsePngDimensions) and
test/unit/maestro-regions.test.js (validateRegionInputs + resolveRegions
coordinate paths) — direct module-level coverage for the now-pure extracted
functions, mirroring the maestro-hierarchy.test.js convention. The existing
api.test.js HTTP-level suite remains the behavior contract (incl. the
non-mocked sync-drain canary and the locateScreenshot filePath/glob/traversal
specs). Remove the now-unused ServerError import from api.js (all its throws
moved into the extracted modules).
@Sriram567 Sriram567 requested a review from a team as a code owner June 16, 2026 06:33
Comment thread packages/core/src/maestro-screenshot.js Fixed
Put platform/sessionId/percy on their own lines in the multiline
resolveRegions() call objects (eslint object-property-newline).
…p join

The api.js .semgrepignore entry was covering two sinks: the maestro-screenshot
path joins (now moved to maestro-screenshot-file.js) AND createStaticServer's
path.posix.join('/', baseUrl, …) sitemap-URL construction. Removing api.js
re-exposed the latter to the path-join-resolve-traversal rule (baseUrl is
trusted server config; filenames are locally globbed *.html). Re-add api.js
with a createStaticServer-focused rationale.
…sign

semgrep (express-data-exfiltration) flagged Object.assign(payload, …) where the
merged object derives from req.body as a mass-assignment risk. The original
inline code set payload.regions / ignoredElementsData / consideredElementsData
explicitly; restore that — resolveRegions returns only those three keys, so
assigning them by name is behavior-identical, coverage-neutral, and avoids the
dynamic merge of request-derived data.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants