Skip to content

Generate Python payload types from a schema derived from shared - #421

Merged
vishnuv688 merged 6 commits into
mainfrom
feat/299-generated-contract-types
Oct 8, 2026
Merged

vishnuv688 merged 6 commits into
mainfrom
feat/299-generated-contract-types

Conversation

@vishnuv688

Copy link
Copy Markdown
Member

Closes #299

The Python adapter hand-mirrored shared's payload types in types.py, and nothing checked the mirror: no mypy runs over the package, so the TypedDicts documented the contract without enforcing it. They had already drifted.

What changes

  • Schema from shared's own types. packages/shared/scripts/wire-schema.ts reads WsPayloadFor and a new TraceExportPayloadFor with the TypeScript compiler and writes one JSON Schema per wire scope to packages/shared/wire-schema.json. Scope names and payloads come from shared's existing maps, not a hand list. On the wire a Date is its ISO string and an Error its SerializedError, and the schema says so. packages/shared/tests/wire-schema.test.ts fails when the committed copy is stale, so the Node CI job guards it.
  • Generated Python types. gen_contract.py renders the schema into _wire_types.py (36 TypedDicts; required and optional keys split across a base class because typing.Required needs 3.11). types.py re-exports them in place of 151 hand-written lines. The existing drift step diffs the new file too.
  • Every sent frame is validated. The eight per-file fake transports in the Python tests become one RecordingTransport that checks each frame against the schema before recording it. Any test that sends anything now fails on an undeclared field, a missing required one, or a null where shared expects the key to be absent.
  • TraceLog.suites is tightened from Record<string, unknown>[] to Record<string, SuiteStats>[], which is what every adapter sends, so suite frames are covered too.

Drift the check found, fixed here

Field Was Now
callSource (commands, tests) null when unknown omitted
metadata.url null when unknown omitted
in-flight request status, endTime null omitted
unfinished suite state null omitted
native session viewport {width, height} adds offsetLeft: 0, offsetTop: 0, scale: 1, as core's fromWindow sends

Every reader of those fields in the app, backend and trace uses ?., ??, truthiness or a typeof check, so absent reads the same as null did. ElementScripts is renamed to shared's ElementScriptsResponse; the change fragment is minor for that reason.

Verification

  • pnpm test (2506 tests), pnpm lint, pnpm typecheck, pnpm build: green.
  • Python unit tests under unittest (707), as CI runs them: green.
  • Regenerating both files produces no diff.

Not verified against a live browser: the change is to payload shapes, and each reader of the changed fields was checked in code instead.

Every adapter sends Record<uid, SuiteStats>[] under the suites scope, but
the type said Record<string, unknown>[], so nothing derived from it could
describe a suite frame. The app's two fixtures build fragments, which the
app reads as such, and are cast at the one place they enter a TraceLog.
An adapter that cannot import shared has had to retype its payloads by
hand. scripts/wire-schema.ts reads WsPayloadFor and the new
TraceExportPayloadFor with the TypeScript compiler and writes one JSON
Schema per scope to wire-schema.json, mapping Date to its ISO string and
Error to SerializedError as they cross the socket. A vitest test fails
when the committed copy is stale, so the schema cannot drift from src.

Refs #299
… frame

types.py hand-mirrored shared and nothing checked the mirror; no mypy runs
over this package, so a TypedDict alone enforces nothing. gen_contract.py
now renders wire-schema.json into _wire_types.py, and the eight
per-file fake transports become one RecordingTransport that validates
each frame against the schema before recording it.

That check found real drift, fixed here: null sent for callSource,
metadata.url, and an in-flight request's status and endTime, and for an
unfinished suite's state, where shared types each as absent; and a
native viewport without offsetLeft, offsetTop and scale, which now
carries 0, 0, 1 as core's fromWindow sends. Every reader uses ?., ?? or
truthiness, so absent reads as null did. ElementScripts is renamed to
shared's ElementScriptsResponse.

Refs #299
The drift step now diffs _wire_types.py beside _contract.py, and the
workflow triggers on packages/shared/wire-schema.json, since the tests
read it and a schema change can turn them red.
@greptile-apps

greptile-apps Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High risk] Regenerates Python payload types from a shared schema.

The PR appears safe to merge; the previous finding is fixed and no new blocking issue was found.

What we checked:

  • Bad frames cannot hide: send_json saves violations instead of raising them. The owning test checks them during cleanup, outside the sender's catch block.

Summary

This PR generates Python payload types from shared's TypeScript types and checks test frames against the generated schema.

  • Adds schema generation and checks for stale generated files.
  • Replaces handwritten Python payload types and fixes fields that had drifted.
  • Fixes the previous review finding by checking saved frame violations during test cleanup.
  • No new actionable issues were found in the changes since the previous review.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Shared TypeScript payload types"] --> B["wire-schema.ts"]
  B --> C["wire-schema.json"]
  C --> D["gen_contract.py"]
  D --> E["_wire_types.py"]
  E --> F["types.py"]
  C --> G["RecordingTransport checks test frames"]
  G --> H["Saved violations fail test cleanup"]
Loading

Reviews (2) · Last reviewed commit: "test(selenium-devtools-py): fail a bad f..." · Reviewed by Greptile

Comment thread packages/selenium-devtools-py/tests/wire_contract.py Outdated
…he send

RecordingTransport raised AssertionError from send_json, but the adapter's
best-effort senders catch every exception, so a bad frame was swallowed
wherever the test only checked the sender's return value. Violations are
now collected and fail the owning test at cleanup, outside those except
blocks. That surfaced two invalid fixtures, corrected here: an action
snapshot of {"a": 1} and a replaced row with no command or args.
@vishnuv688
vishnuv688 merged commit 4fc91f2 into main Oct 8, 2026
13 checks passed
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.

Generate contract types, not just scope names

1 participant