v0.20.0
Added
-
A same-origin framed
ClickandPress … buttonnow dispatch a real
trusted click, not a refusal. A classic SAP GUI for HTML screen embeds
its whole UI in a same-origin iframe, and every tab strip, button, and
field on it lived behind that frame boundary. Clicking one was refused
outright: a pointer action inside a frame could only ever reach it as an
untrusted synthetic event, which an application checkingisTrustedis
free to ignore while the step still reports success - a real
release-without-effect false green, so the refusal was the honest answer
at the time. It no longer has to be. The driver now computes the target's
page-absolute point (the frame's own offset in the parent document, plus
the element's offset inside the frame) and dispatches the same
Input.dispatchMouseEventsequence the top-level document's click
already used - the click is real,isTrustedis true, and an application
driven this way cannot tell the difference from a person.Hover,
Double-click,Right-click, andUploadinside a frame stay refused;
none has this point-computation wired up yet. -
A framed
Typeinto a real<input>/<textarea>now types for real.
Framed value-writes previously set.valuethrough the native setter and
fired syntheticinput/changeevents - correct for anything listening
to those events, and wrong for anything that is not. A live SAP GUI for
HTML screen tracks its own field state off real keyboard events, the same
way its desktop ancestor always did, and never saw a synthetic write at
all: the field stayed visibly empty no matter what the trace said it
typed. The driver now clicks the field for real, selects any existing
value, and types the real keystrokes CDP would generate for an actual
person - the same trusted path the top-level document'sTypealready
used. Anything framed that is not a native input (a contenteditable, a
custom widget) keeps the synthetic write, since there is no native
keystroke target to land on. -
A framed element is now found by the label its OWN form control
resolves to, not by matching arbitrary text.the "Material" in the iframe "X"used to search the frame for any element whose raw
textContentread exactly"Material"- which, on a screen where the
visible label and the input it labels are separate DOM nodes (common
outside hand-rolled markup), matched the label itself, never the field.
Typing then landed on a<div>, silently no-op'd through the
now-corrected trusted-keystroke path, and the search it was meant to fill
in ran empty. Both the write and read paths now check a form control's
native.labelsassociation first - the same relationshipscene()
already uses to report a field's label back to authoring - before falling
back to the old plain-text search, so a label naming a real field finds
that field. -
flowproof recordshows the browser by default;--headed/--headless
make the choice a flag, not tribal knowledge. Watching awebrecording
has been possible sinceFLOWPROOF_HEADEDshipped, but only if you already
knew the variable existed - nothing in--helpsaid so, and a new user
evaluating flowproof against a tool like Tosca (where you always watch the
browser while recording) got a blank terminal by default. Recording is the
one human-in-the-loop step, so it now opens Chromium unless told not to;
flowproof run(replay) is unchanged and stays headless, because it is
meant to run unattended on CI runners that often have no display at all,
and a default flip there risks the false-green class of bug this project
exists to catch - headed Chromium sizes its window from the desktop, so a
visual baseline recorded one way and replayed the other reports a mismatch
that has nothing to do with the flow. Anything that scriptsflowproof recordwithout a person watching (CI, a container, a cassette-generation
pipeline) needs--headlessnow; everything else needs nothing at all. -
The SAP test simulator models more than one screen shape. Every
automated SAP check ran against the same flat VA01 window - onewnd[0],
every field hanging straight off it. A fixture like that cannot fail.
Window scoping could break, nesting could break, and nothing short of a
person sitting at the one real SAP machine the team has would notice.The simulator now serves two more shapes. A classic
GuiTableControl
carries its cells as CHILDREN, under SAP's real[column,row]ids, so the
tree walk has to recurse past depth two andFindByIdhas to survive an id
with brackets and a comma in it. Back opens awnd[1]modal that sits in
the session tree besidewnd[0]and leaves it again when dismissed - and
the two windows deliberately share no text, so which window a reader is
reading is a question with an answer.sap_sim_e2edrives both through the production COM engine.
Fixed
-
The Python SDK's dev/test lock resolved a known-vulnerable
pytestfor
Python 3.9.pytest < 9.0.3has insecure/tmp/pytest-of-{user}
handling on UNIX (GHSA-6w46-j5rx-g56g); the fix only ever shipped in the
9.x line, and 9.x dropped Python 3.9 support outright, so a plain floor
bump was unsatisfiable for the 3.9 branch of the lock. Thedev
dependency group now declares its ownrequires-python = ">=3.10"
([tool.uv.dependency-groups]), separate from the package's own
requires-python = ">=3.9"— running the test suite from source now needs
3.10+, same as CI already effectively runs, while nothing changes for
anyone installing the shippedflowproofpackage on 3.9. The lock now
resolves onepytestversion everywhere: 9.1.1, patched. -
The nightly SAP suite was failing on the same field, every night, for a
reason nobody had looked for. Every scheduledsap-e2erun since early
August had failed on the first field of every flow that types into one —
while the one flow that never touches a field kept passing. The heartbeat
script written to keep an idle SAP session alive (#453) had nothing to do
with it: it isn't invoked from the workflow at all, and even where it was
separately registered on the runner, its scheduled task had no
-Principal, so it silently stopped reaching the interactive desktop the
moment that session's RDP connection went from active to disconnected.The actual failure was inside the job itself.
sap-session-bootstrap.ps1
logs the session in once, at job start; the login is genuinely fine at
that point, confirmed bycargo test sap_e2epassing cleanly a few
minutes later. But two fullcargo builds follow with no shared
incremental cache — around seven minutes of pure compilation, untouched
by any SAP activity — and by the time the flow suite starts, the
application server has logged the session out for sitting idle. The
client raises its own "maximum user idle time exceeded" notice, a native
Win32 dialog outside SAP's scripting object model — invisible to
FindById, so nothing already in the driver could see or close it, and
every field lookup after it just timed out waiting for a screen that was
never going to render.sap-session-bootstrap.ps1now recognizes that dialog and dismisses it
(declining, not accepting — the default button opens a second window
that would need dismissing too), andsap-e2e.ymlruns bootstrap a
second time immediately before the flow suite, not only once at job
start, closing the actual idle window instead of one that had already
passed.