flowproof 0.23.4__tar.gz → 0.23.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flowproof-0.23.4 → flowproof-0.23.6}/Cargo.lock +7 -7
- {flowproof-0.23.4 → flowproof-0.23.6}/Cargo.toml +1 -1
- {flowproof-0.23.4 → flowproof-0.23.6}/PKG-INFO +1 -1
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/web.rs +238 -60
- flowproof-0.23.6/crates/flowproof-adapters/tests/visibility_detach_e2e.rs +77 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/author.rs +24 -6
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/Cargo.toml +4 -2
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/lib.rs +21 -0
- flowproof-0.23.6/crates/flowproof-cli/tests/agent_url_record_e2e.rs +478 -0
- flowproof-0.23.6/crates/flowproof-cli/tests/sigterm_shared_browser_e2e.rs +153 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/flowproof/__init__.py +1 -1
- {flowproof-0.23.4 → flowproof-0.23.6}/pyproject.toml +1 -1
- flowproof-0.23.4/crates/flowproof-cli/tests/agent_url_record_e2e.rs +0 -216
- {flowproof-0.23.4 → flowproof-0.23.6}/README.md +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/Cargo.toml +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/agent_proxy.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/agent_runner.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_linux.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/audit.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/capture.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/env_block.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/filters.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/identity.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/logon.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/netevents.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/run.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/spawn.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows/wfp.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/egress_windows.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/fiori.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/fs_observe.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/lib.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/mcp_core.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/mcp_http.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/mcp_stdio.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/sap_com.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/src/vision.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/a11y_capture.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/drag_spike.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/fs_observation_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/grid_scene_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/network_idle_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/shared_browser_shutdown_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/side_effect_http_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/static_area_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-adapters/tests/windows_containment_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/Cargo.toml +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/agent_steps.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/clarify.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/doc_author.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/doc_formats/hp_alm.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/doc_formats.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/draft_assembly.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/heal.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/lib.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/llm.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/recorder.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/repair.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/rules.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/src/spec.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-agent/tests/model_client_http.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/skills/flowproof-config/SKILL.md +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/agent_flow.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/capture.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/config.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/doctor.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/main.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/suite.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/suite_checkpoint.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/suite_inputs.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/suite_journal.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/src/update_check.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/a11y_selector_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/agent_conversation_anthropic_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/agent_conversation_streaming_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/agent_flow_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/api_pipeline.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/audit_record_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/calc_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/clock_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/config_cli_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/config_seed_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/config_skill_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/cookie_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/doctor_ai_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/doctor_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/documented_flags.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/egress_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/examples_resolve.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_arg_matchers.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_audit_since.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_call_order.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_capability_regression.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_control_verdict.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_mcp_kill.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_no_tool_call.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_reply_contains.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/falsifiability_side_effect.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/fiori_field_commit_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/grid_cell_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/iframe_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/llm_author_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/mcp_http_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/mcp_stdio_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/notepad_author_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/notepad_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/page_title_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/pinned_random_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/sap_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/sap_pipeline.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/sap_sim_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/scoped_container_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/select_missing_option_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/skip_unless_env.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/suite_checkpoint.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/suite_env_from.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/suite_flow_isolation.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/suite_missing_trace.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/suite_policy.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/support/sap_simulator.py +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/visibility_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/vision_pipeline.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-cli/tests/web_e2e.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/Cargo.toml +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/app.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/backend.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/gdi.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/lib.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/mock.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/oob.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/recording.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/redact.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/surface.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/visual.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-driver/src/window.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-python/Cargo.toml +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-python/src/lib.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-replay/Cargo.toml +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-replay/src/lib.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-replay/src/report.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-replay/src/runrecord.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-replay/tests/replay_calc.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/Cargo.toml +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/schema/cassette-v1.schema.json +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/schema/side-effect-v1.schema.json +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/schema/trace-v1.schema.json +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/captures.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/cassette.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/cassette_diff.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/egress.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/format.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/lib.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/secret.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/secret_scan.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/side_effect.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/substitution.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/src/toolcalls.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/tests/cassette_conformance.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/tests/fixtures/multi.trace.jsonl +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/tests/fixtures/sample.trace.jsonl +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/tests/schema_conformance.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/crates/flowproof-trace/tests/side_effect_conformance.rs +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/flowproof/cli.py +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/flowproof/flow.py +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/flowproof/mcp_server.py +0 -0
- {flowproof-0.23.4 → flowproof-0.23.6}/flowproof/py.typed +0 -0
|
@@ -930,7 +930,7 @@ dependencies = [
|
|
|
930
930
|
|
|
931
931
|
[[package]]
|
|
932
932
|
name = "flowproof-adapters"
|
|
933
|
-
version = "0.23.
|
|
933
|
+
version = "0.23.6"
|
|
934
934
|
dependencies = [
|
|
935
935
|
"anyhow",
|
|
936
936
|
"flowproof-driver",
|
|
@@ -949,7 +949,7 @@ dependencies = [
|
|
|
949
949
|
|
|
950
950
|
[[package]]
|
|
951
951
|
name = "flowproof-agent"
|
|
952
|
-
version = "0.23.
|
|
952
|
+
version = "0.23.6"
|
|
953
953
|
dependencies = [
|
|
954
954
|
"chrono",
|
|
955
955
|
"flowproof-driver",
|
|
@@ -967,7 +967,7 @@ dependencies = [
|
|
|
967
967
|
|
|
968
968
|
[[package]]
|
|
969
969
|
name = "flowproof-cli"
|
|
970
|
-
version = "0.23.
|
|
970
|
+
version = "0.23.6"
|
|
971
971
|
dependencies = [
|
|
972
972
|
"ab_glyph",
|
|
973
973
|
"chrono",
|
|
@@ -992,7 +992,7 @@ dependencies = [
|
|
|
992
992
|
|
|
993
993
|
[[package]]
|
|
994
994
|
name = "flowproof-driver"
|
|
995
|
-
version = "0.23.
|
|
995
|
+
version = "0.23.6"
|
|
996
996
|
dependencies = [
|
|
997
997
|
"calamine",
|
|
998
998
|
"flowproof-trace",
|
|
@@ -1008,7 +1008,7 @@ dependencies = [
|
|
|
1008
1008
|
|
|
1009
1009
|
[[package]]
|
|
1010
1010
|
name = "flowproof-python"
|
|
1011
|
-
version = "0.23.
|
|
1011
|
+
version = "0.23.6"
|
|
1012
1012
|
dependencies = [
|
|
1013
1013
|
"flowproof-agent",
|
|
1014
1014
|
"flowproof-cli",
|
|
@@ -1020,7 +1020,7 @@ dependencies = [
|
|
|
1020
1020
|
|
|
1021
1021
|
[[package]]
|
|
1022
1022
|
name = "flowproof-replay"
|
|
1023
|
-
version = "0.23.
|
|
1023
|
+
version = "0.23.6"
|
|
1024
1024
|
dependencies = [
|
|
1025
1025
|
"chrono",
|
|
1026
1026
|
"flowproof-agent",
|
|
@@ -1034,7 +1034,7 @@ dependencies = [
|
|
|
1034
1034
|
|
|
1035
1035
|
[[package]]
|
|
1036
1036
|
name = "flowproof-trace"
|
|
1037
|
-
version = "0.23.
|
|
1037
|
+
version = "0.23.6"
|
|
1038
1038
|
dependencies = [
|
|
1039
1039
|
"jsonschema",
|
|
1040
1040
|
"regex",
|
|
@@ -22,7 +22,7 @@ members = ["crates/flowproof-driver", "crates/flowproof-trace", "crates/flowproo
|
|
|
22
22
|
exclude = ["spike/windows-containment"]
|
|
23
23
|
|
|
24
24
|
[workspace.package]
|
|
25
|
-
version = "0.23.
|
|
25
|
+
version = "0.23.6"
|
|
26
26
|
edition = "2021"
|
|
27
27
|
license = "Apache-2.0"
|
|
28
28
|
repository = "https://github.com/automators-com/flowproof"
|
|
@@ -459,10 +459,23 @@ const FRAME_ACT: &str = r#"function(FRAME, CSS, ID, TEXT, OP, ARG){
|
|
|
459
459
|
+ '.urMnu, .lsPOMNContainer, [id^="ARIA_"], [id*="SAPGUI"]'
|
|
460
460
|
);
|
|
461
461
|
}
|
|
462
|
+
// The classic value-help ("F4") popup's own filter input is a document-
|
|
463
|
+
// level match for isSapWebGui() too (it renders inside the same WebGUI
|
|
464
|
+
// frame), but it does NOT commit on blur like an ordinary screen field -
|
|
465
|
+
// it commits on an explicit Go/search button click, which the recorded
|
|
466
|
+
// flow already does as its own separate step. Confirmed live: typing a
|
|
467
|
+
// value then tabbing out of one of these (the sap_webgui path's blur+
|
|
468
|
+
// verify dance) left the field empty rather than reformatted, failing a
|
|
469
|
+
// spec that never asked for a Tab here at all. `NSH`-prefixed ids and
|
|
470
|
+
// `SHLP`/search-help markers are SAP's own standard naming for this popup
|
|
471
|
+
// across systems, not something specific to one client's screens.
|
|
472
|
+
function isValueHelpPopupField(){
|
|
473
|
+
return !!el.closest('[id^="NSH"], [id*="SHLP"], [id*="F4Help"], [class*="shlp"]');
|
|
474
|
+
}
|
|
462
475
|
if (OP === 'kind'){
|
|
463
476
|
var nativeInput = el.tagName === 'INPUT' || el.tagName === 'TEXTAREA';
|
|
464
477
|
return (nativeInput ? 'native_input' : 'other') + ':'
|
|
465
|
-
+ (isSapWebGui() ? 'sap_webgui' : 'generic');
|
|
478
|
+
+ (isSapWebGui() && !isValueHelpPopupField() ? 'sap_webgui' : 'generic');
|
|
466
479
|
}
|
|
467
480
|
if (OP === 'value'){
|
|
468
481
|
var value = ('value' in el)
|
|
@@ -2078,27 +2091,38 @@ impl WebAppDriver {
|
|
|
2078
2091
|
))
|
|
2079
2092
|
}
|
|
2080
2093
|
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2094
|
+
// These probes run before dispatching any input. A control without a
|
|
2095
|
+
// box during a redraw is not ready; let the bounded actionability wait
|
|
2096
|
+
// re-resolve it, without retrying a click or masking transport errors.
|
|
2097
|
+
fn composed_readiness(&mut self, target: &UiaSelector) -> Result<Option<String>, DriverError> {
|
|
2098
|
+
match flowproof_driver::composed_actionability_gate(self, target) {
|
|
2099
|
+
Err(DriverError::Browser(message))
|
|
2100
|
+
if message.contains("box model of [")
|
|
2101
|
+
&& message.contains("Could not compute box model") =>
|
|
2102
|
+
{
|
|
2103
|
+
Ok(Some(
|
|
2104
|
+
"not rendered (target has no box during redraw)".into(),
|
|
2105
|
+
))
|
|
2106
|
+
}
|
|
2107
|
+
result => result,
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
/// Run an element operation with the shared transport-fault retry
|
|
2112
|
+
/// policy (see [`retry_on_transport_fault`]): re-resolve the element
|
|
2113
|
+
/// (its object id may be gone with the dead connection) and try again
|
|
2114
|
+
/// before failing the step.
|
|
2084
2115
|
fn with_element<T>(
|
|
2085
2116
|
&self,
|
|
2086
2117
|
locator: &WebLocator,
|
|
2087
2118
|
context: &str,
|
|
2088
2119
|
op: impl Fn(&headless_chrome::Element<'_>) -> Result<T, anyhow::Error>,
|
|
2089
2120
|
) -> Result<T, DriverError> {
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
Err(e) if !retried && is_transport_fault(&e.to_string()) => {
|
|
2096
|
-
retried = true;
|
|
2097
|
-
std::thread::sleep(Duration::from_millis(300));
|
|
2098
|
-
}
|
|
2099
|
-
Err(e) => return Err(web_err(context, e)),
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2121
|
+
retry_on_transport_fault(|| {
|
|
2122
|
+
let element = self.find(locator).map_err(|e| e.to_string())?;
|
|
2123
|
+
op(&element).map_err(|e| e.to_string())
|
|
2124
|
+
})
|
|
2125
|
+
.map_err(|e| web_err(context, e))
|
|
2102
2126
|
}
|
|
2103
2127
|
}
|
|
2104
2128
|
|
|
@@ -2120,30 +2144,63 @@ fn is_transport_fault(message: &str) -> bool {
|
|
|
2120
2144
|
|| m.contains("unable to make method calls")
|
|
2121
2145
|
}
|
|
2122
2146
|
|
|
2123
|
-
///
|
|
2124
|
-
/// a
|
|
2125
|
-
///
|
|
2126
|
-
///
|
|
2127
|
-
///
|
|
2128
|
-
///
|
|
2129
|
-
///
|
|
2130
|
-
|
|
2147
|
+
/// Attempts allowed beyond the first for a transport-faulting operation.
|
|
2148
|
+
/// Was 1 (a single fixed ~300ms retry) until issue #608: a clean live
|
|
2149
|
+
/// re-verification of `medium-02-change-info-record-readonly.flow.yaml`
|
|
2150
|
+
/// still hit "underlying connection is closed" 3 of 6 runs, including
|
|
2151
|
+
/// `frame_act` failing *twice in a row* even though it already had that one
|
|
2152
|
+
/// retry - proof a single ~300ms retry does not reliably bridge a real CDP
|
|
2153
|
+
/// outage.
|
|
2154
|
+
const TRANSPORT_FAULT_RETRIES: u32 = 3;
|
|
2155
|
+
|
|
2156
|
+
/// Backoff base: the wait before retry N is `N * TRANSPORT_FAULT_BACKOFF`
|
|
2157
|
+
/// (300ms, 600ms, 900ms), so the delay grows instead of hammering a
|
|
2158
|
+
/// connection that needs a moment to recover.
|
|
2159
|
+
const TRANSPORT_FAULT_BACKOFF: Duration = Duration::from_millis(300);
|
|
2160
|
+
|
|
2161
|
+
/// Retry a fallible operation on a transient CDP transport fault, backing
|
|
2162
|
+
/// off a little more between each attempt - the same recovery
|
|
2163
|
+
/// `with_element` gives element-scoped calls (re-finding the element and
|
|
2164
|
+
/// retrying its own op), generalized here for a call that talks to the tab
|
|
2165
|
+
/// directly and has nothing to re-resolve. Pure control flow, no CDP calls
|
|
2166
|
+
/// of its own, so it is unit-testable with a mock closure rather than only
|
|
2167
|
+
/// provable against a real, flaky browser.
|
|
2168
|
+
fn retry_on_transport_fault<T>(
|
|
2131
2169
|
mut attempt: impl FnMut() -> Result<T, String>,
|
|
2132
2170
|
) -> Result<T, String> {
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2171
|
+
retry_on_transport_fault_with(TRANSPORT_FAULT_RETRIES, &mut attempt, |backoff| {
|
|
2172
|
+
std::thread::sleep(backoff)
|
|
2173
|
+
})
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
/// The retry loop itself, with the attempt budget and the sleep both
|
|
2177
|
+
/// injectable: production always calls it via [`retry_on_transport_fault`]
|
|
2178
|
+
/// with the real constants and a real sleep; tests inject a smaller budget
|
|
2179
|
+
/// and a no-op sleep so the backoff schedule is provable without paying
|
|
2180
|
+
/// seconds of real wall-clock time per test.
|
|
2181
|
+
fn retry_on_transport_fault_with<T>(
|
|
2182
|
+
max_retries: u32,
|
|
2183
|
+
attempt: &mut impl FnMut() -> Result<T, String>,
|
|
2184
|
+
mut sleep: impl FnMut(Duration),
|
|
2185
|
+
) -> Result<T, String> {
|
|
2186
|
+
let mut retries = 0;
|
|
2187
|
+
loop {
|
|
2188
|
+
match attempt() {
|
|
2189
|
+
Ok(value) => return Ok(value),
|
|
2190
|
+
Err(e) if retries < max_retries && is_transport_fault(&e) => {
|
|
2191
|
+
retries += 1;
|
|
2192
|
+
sleep(TRANSPORT_FAULT_BACKOFF * retries);
|
|
2193
|
+
}
|
|
2194
|
+
Err(e) => return Err(e),
|
|
2138
2195
|
}
|
|
2139
|
-
Err(e) => Err(e),
|
|
2140
2196
|
}
|
|
2141
2197
|
}
|
|
2142
2198
|
|
|
2143
2199
|
#[cfg(test)]
|
|
2144
2200
|
mod transport_retry_tests {
|
|
2145
|
-
use super::
|
|
2146
|
-
use std::cell::Cell;
|
|
2201
|
+
use super::{retry_on_transport_fault, retry_on_transport_fault_with};
|
|
2202
|
+
use std::cell::{Cell, RefCell};
|
|
2203
|
+
use std::time::Duration;
|
|
2147
2204
|
|
|
2148
2205
|
/// docs/fiori-reliability/FINDINGS.md:
|
|
2149
2206
|
/// medium-02-change-info-record-readonly.flow.yaml failed live with
|
|
@@ -2154,7 +2211,7 @@ mod transport_retry_tests {
|
|
|
2154
2211
|
#[test]
|
|
2155
2212
|
fn a_transport_fault_recovers_on_the_second_attempt() {
|
|
2156
2213
|
let calls = Cell::new(0);
|
|
2157
|
-
let result =
|
|
2214
|
+
let result = retry_on_transport_fault(|| {
|
|
2158
2215
|
calls.set(calls.get() + 1);
|
|
2159
2216
|
if calls.get() == 1 {
|
|
2160
2217
|
Err("Unable to make method calls because underlying connection is closed".into())
|
|
@@ -2163,21 +2220,51 @@ mod transport_retry_tests {
|
|
|
2163
2220
|
}
|
|
2164
2221
|
});
|
|
2165
2222
|
assert_eq!(result, Ok("recovered"));
|
|
2166
|
-
assert_eq!(calls.get(), 2
|
|
2223
|
+
assert_eq!(calls.get(), 2);
|
|
2167
2224
|
}
|
|
2168
2225
|
|
|
2226
|
+
/// issue #608: a live run saw `frame_act` fail twice in a row on the
|
|
2227
|
+
/// same connection-closed fault even though it already had one retry -
|
|
2228
|
+
/// this proves the policy now survives that exact shape of failure by
|
|
2229
|
+
/// recovering on the third attempt (second retry).
|
|
2169
2230
|
#[test]
|
|
2170
|
-
fn
|
|
2231
|
+
fn a_transport_fault_recovers_on_the_third_attempt() {
|
|
2171
2232
|
let calls = Cell::new(0);
|
|
2172
|
-
let result
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2233
|
+
let result = retry_on_transport_fault_with(
|
|
2234
|
+
3,
|
|
2235
|
+
&mut || {
|
|
2236
|
+
calls.set(calls.get() + 1);
|
|
2237
|
+
if calls.get() < 3 {
|
|
2238
|
+
Err(
|
|
2239
|
+
"Unable to make method calls because underlying connection is closed"
|
|
2240
|
+
.into(),
|
|
2241
|
+
)
|
|
2242
|
+
} else {
|
|
2243
|
+
Ok("recovered")
|
|
2244
|
+
}
|
|
2245
|
+
},
|
|
2246
|
+
|_| {},
|
|
2247
|
+
);
|
|
2248
|
+
assert_eq!(result, Ok("recovered"));
|
|
2249
|
+
assert_eq!(calls.get(), 3, "must survive two consecutive faults");
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
#[test]
|
|
2253
|
+
fn a_transport_fault_that_never_clears_still_fails_after_all_retries() {
|
|
2254
|
+
let calls = Cell::new(0);
|
|
2255
|
+
let result: Result<(), String> = retry_on_transport_fault_with(
|
|
2256
|
+
3,
|
|
2257
|
+
&mut || {
|
|
2258
|
+
calls.set(calls.get() + 1);
|
|
2259
|
+
Err("the event waited for never came".into())
|
|
2260
|
+
},
|
|
2261
|
+
|_| {},
|
|
2262
|
+
);
|
|
2176
2263
|
assert_eq!(result, Err("the event waited for never came".to_string()));
|
|
2177
2264
|
assert_eq!(
|
|
2178
2265
|
calls.get(),
|
|
2179
|
-
|
|
2180
|
-
"one
|
|
2266
|
+
4,
|
|
2267
|
+
"one first attempt plus three retries, then give up - not an infinite loop"
|
|
2181
2268
|
);
|
|
2182
2269
|
}
|
|
2183
2270
|
|
|
@@ -2187,13 +2274,34 @@ mod transport_retry_tests {
|
|
|
2187
2274
|
#[test]
|
|
2188
2275
|
fn a_non_transport_error_is_never_retried() {
|
|
2189
2276
|
let calls = Cell::new(0);
|
|
2190
|
-
let result: Result<(), String> =
|
|
2277
|
+
let result: Result<(), String> = retry_on_transport_fault(|| {
|
|
2191
2278
|
calls.set(calls.get() + 1);
|
|
2192
2279
|
Err("element not found".into())
|
|
2193
2280
|
});
|
|
2194
2281
|
assert_eq!(result, Err("element not found".to_string()));
|
|
2195
2282
|
assert_eq!(calls.get(), 1, "a non-transport error must not be retried");
|
|
2196
2283
|
}
|
|
2284
|
+
|
|
2285
|
+
/// The wait between attempts grows (300ms, 600ms, 900ms) instead of
|
|
2286
|
+
/// staying fixed - issue #608's "and/or backoff that scales" direction.
|
|
2287
|
+
#[test]
|
|
2288
|
+
fn backoff_grows_with_each_retry() {
|
|
2289
|
+
let waits = RefCell::new(Vec::new());
|
|
2290
|
+
let result: Result<(), String> = retry_on_transport_fault_with(
|
|
2291
|
+
3,
|
|
2292
|
+
&mut || Err("connection is closed".into()),
|
|
2293
|
+
|backoff| waits.borrow_mut().push(backoff),
|
|
2294
|
+
);
|
|
2295
|
+
assert!(result.is_err());
|
|
2296
|
+
assert_eq!(
|
|
2297
|
+
waits.into_inner(),
|
|
2298
|
+
vec![
|
|
2299
|
+
Duration::from_millis(300),
|
|
2300
|
+
Duration::from_millis(600),
|
|
2301
|
+
Duration::from_millis(900),
|
|
2302
|
+
]
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2197
2305
|
}
|
|
2198
2306
|
|
|
2199
2307
|
/// How a [`UiaSelector`] resolves on a page: a CSS selector or a text
|
|
@@ -2473,8 +2581,10 @@ impl WebAppDriver {
|
|
|
2473
2581
|
// this talks to the tab directly, with no element to re-resolve, so
|
|
2474
2582
|
// it never went through with_element's retry either - one transient
|
|
2475
2583
|
// CDP hiccup here (this is the exact call `type_text`'s framed path
|
|
2476
|
-
// uses for every field inside an iframe) killed the whole flow.
|
|
2477
|
-
|
|
2584
|
+
// uses for every field inside an iframe) killed the whole flow. A
|
|
2585
|
+
// single retry was also not enough on its own (issue #608) - this
|
|
2586
|
+
// is the call site that failed twice in a row live.
|
|
2587
|
+
let status = retry_on_transport_fault(|| {
|
|
2478
2588
|
self.tab()
|
|
2479
2589
|
.map_err(|e| e.to_string())?
|
|
2480
2590
|
.evaluate(&call, false)
|
|
@@ -3366,7 +3476,7 @@ impl AppDriver for WebAppDriver {
|
|
|
3366
3476
|
})
|
|
3367
3477
|
.flatten();
|
|
3368
3478
|
let Some(resolver) = resolver else {
|
|
3369
|
-
return
|
|
3479
|
+
return self.composed_readiness(target);
|
|
3370
3480
|
};
|
|
3371
3481
|
let gate_js = format!(
|
|
3372
3482
|
"(async el => {{
|
|
@@ -3402,7 +3512,7 @@ impl AppDriver for WebAppDriver {
|
|
|
3402
3512
|
.map_err(|e| web_err(&format!("actionability of [{target}]"), e))?;
|
|
3403
3513
|
let verdict = value.value.and_then(|v| v.as_str().map(str::to_string));
|
|
3404
3514
|
Ok(match verdict.as_deref() {
|
|
3405
|
-
Some("sap_grid") => return
|
|
3515
|
+
Some("sap_grid") => return self.composed_readiness(target),
|
|
3406
3516
|
Some("ok") => None,
|
|
3407
3517
|
Some("disabled") => Some("disabled".into()),
|
|
3408
3518
|
Some("unstable") => Some("unstable (still moving/animating)".into()),
|
|
@@ -3454,6 +3564,36 @@ impl AppDriver for WebAppDriver {
|
|
|
3454
3564
|
|
|
3455
3565
|
fn element_visible(&mut self, selector: &UiaSelector) -> Result<Option<bool>, DriverError> {
|
|
3456
3566
|
let locator = Self::locator(selector)?;
|
|
3567
|
+
// A dialog can disappear between element_exists and this probe.
|
|
3568
|
+
// Resolve and read in one page operation: absence is a visibility
|
|
3569
|
+
// answer, while browser/transport failures must still propagate.
|
|
3570
|
+
if selector.frame.is_none() {
|
|
3571
|
+
if let Some(resolver) = Self::js_resolver(&locator) {
|
|
3572
|
+
let expression = format!(
|
|
3573
|
+
"((element) => {{
|
|
3574
|
+
if (!element) return false;
|
|
3575
|
+
if (typeof element.checkVisibility === 'function' &&
|
|
3576
|
+
!element.checkVisibility({{
|
|
3577
|
+
contentVisibilityAuto: true,
|
|
3578
|
+
opacityProperty: true,
|
|
3579
|
+
visibilityProperty: true
|
|
3580
|
+
}})) return false;
|
|
3581
|
+
return element.getClientRects().length > 0;
|
|
3582
|
+
}})({resolver})"
|
|
3583
|
+
);
|
|
3584
|
+
let value = self
|
|
3585
|
+
.tab()?
|
|
3586
|
+
.evaluate(&expression, false)
|
|
3587
|
+
.map_err(|e| web_err(&format!("reading visibility of [{selector}]"), e))?;
|
|
3588
|
+
return value
|
|
3589
|
+
.value
|
|
3590
|
+
.and_then(|v| v.as_bool())
|
|
3591
|
+
.map(Some)
|
|
3592
|
+
.ok_or_else(|| {
|
|
3593
|
+
DriverError::Browser("visibility probe did not return a boolean".into())
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3457
3597
|
let value = self.with_element(
|
|
3458
3598
|
&locator,
|
|
3459
3599
|
&format!("reading visibility of [{selector}]"),
|
|
@@ -4165,14 +4305,14 @@ impl AppDriver for WebAppDriver {
|
|
|
4165
4305
|
id = js(&query.inner_id),
|
|
4166
4306
|
text = js(&query.inner_text),
|
|
4167
4307
|
);
|
|
4168
|
-
//
|
|
4308
|
+
// Retry on a transport fault, same recovery `with_element` gives
|
|
4169
4309
|
// element-scoped calls (see medium-02-change-info-record-readonly's
|
|
4170
4310
|
// real "probing an iframe: ... underlying connection is closed"
|
|
4171
4311
|
// failure in docs/fiori-reliability/FINDINGS.md): this call talks to
|
|
4172
4312
|
// the tab directly, with no element to re-resolve, so it never went
|
|
4173
4313
|
// through that retry at all - any transient CDP hiccup here killed
|
|
4174
4314
|
// the whole flow on the first blip, with zero chance to recover.
|
|
4175
|
-
let status =
|
|
4315
|
+
let status = retry_on_transport_fault(|| {
|
|
4176
4316
|
self.tab()
|
|
4177
4317
|
.map_err(|e| e.to_string())?
|
|
4178
4318
|
.evaluate(&call, false)
|
|
@@ -4287,15 +4427,38 @@ impl AppDriver for WebAppDriver {
|
|
|
4287
4427
|
// back the accepted value before passing.
|
|
4288
4428
|
self.press_key("A", &[KeyMod::Ctrl])?;
|
|
4289
4429
|
self.press_key("Backspace", &[])?;
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4430
|
+
// Talks to the tab directly, same gap frame_act had before
|
|
4431
|
+
// its own retry: confirmed live, a dropped CDP event here
|
|
4432
|
+
// failed the whole flow on a field that would have typed
|
|
4433
|
+
// fine a moment later.
|
|
4434
|
+
retry_on_transport_fault(|| {
|
|
4435
|
+
self.tab()
|
|
4436
|
+
.map_err(|e| e.to_string())?
|
|
4437
|
+
.type_str(text)
|
|
4438
|
+
.map_err(|e| e.to_string())
|
|
4439
|
+
})
|
|
4440
|
+
.map_err(|e| web_err("typing into the framed SAP field", e))?;
|
|
4293
4441
|
self.press_key("Tab", &[])?;
|
|
4294
|
-
|
|
4295
|
-
|
|
4442
|
+
// Some SAP WebGUI fields (a material number checked
|
|
4443
|
+
// against the current plant/org, for instance) validate
|
|
4444
|
+
// against the backend on commit, and read back empty for
|
|
4445
|
+
// a moment while that round trip is in flight - not
|
|
4446
|
+
// reverted, just not settled yet. A single fixed sleep
|
|
4447
|
+
// caught that transitional empty state and failed a
|
|
4448
|
+
// field that would have read back correctly moments
|
|
4449
|
+
// later (confirmed live). Poll instead of sampling once.
|
|
4450
|
+
let deadline = std::time::Instant::now() + Duration::from_secs(2);
|
|
4451
|
+
let accepted = loop {
|
|
4452
|
+
let value = self.frame_value(&query)?;
|
|
4453
|
+
if value == text || std::time::Instant::now() >= deadline {
|
|
4454
|
+
break value;
|
|
4455
|
+
}
|
|
4456
|
+
std::thread::sleep(Duration::from_millis(200));
|
|
4457
|
+
};
|
|
4296
4458
|
if accepted != text {
|
|
4297
4459
|
return Err(DriverError::Browser(format!(
|
|
4298
|
-
"the field inside iframe '{}' accepted a different value after
|
|
4460
|
+
"the field inside iframe '{}' accepted a different value after \
|
|
4461
|
+
commit: typed {text:?}, field now reads {accepted:?}",
|
|
4299
4462
|
query.frame
|
|
4300
4463
|
)));
|
|
4301
4464
|
}
|
|
@@ -4305,9 +4468,13 @@ impl AppDriver for WebAppDriver {
|
|
|
4305
4468
|
// it - the same click -> select -> keystrokes contract the
|
|
4306
4469
|
// top-level path below uses, not an append.
|
|
4307
4470
|
self.frame_act(&query, "select_all", serde_json::Value::Null)?;
|
|
4308
|
-
|
|
4309
|
-
.
|
|
4310
|
-
|
|
4471
|
+
retry_on_transport_fault(|| {
|
|
4472
|
+
self.tab()
|
|
4473
|
+
.map_err(|e| e.to_string())?
|
|
4474
|
+
.type_str(text)
|
|
4475
|
+
.map_err(|e| e.to_string())
|
|
4476
|
+
})
|
|
4477
|
+
.map_err(|e| web_err("typing into the framed field", e))?;
|
|
4311
4478
|
// An Escape here was tried to dismiss the value-help popup
|
|
4312
4479
|
// real keystrokes can trigger on classic SAP GUI screens
|
|
4313
4480
|
// (confirmed live), but Escape on these fields REVERTS to
|
|
@@ -4697,9 +4864,20 @@ impl AppDriver for WebAppDriver {
|
|
|
4697
4864
|
"Space" | "Spacebar" => " ",
|
|
4698
4865
|
_ => key,
|
|
4699
4866
|
};
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4867
|
+
// Same direct-tab gap type_text's framed path had: no element to
|
|
4868
|
+
// re-resolve, so with_element's retry never covered it either -
|
|
4869
|
+
// confirmed live, a dropped CDP event on a bare keypress (Tab to
|
|
4870
|
+
// commit a field, Ctrl+A to select) failed the whole flow.
|
|
4871
|
+
retry_on_transport_fault(|| {
|
|
4872
|
+
self.tab()
|
|
4873
|
+
.map_err(|e| e.to_string())?
|
|
4874
|
+
.press_key_with_modifiers(
|
|
4875
|
+
browser_key,
|
|
4876
|
+
(!mods.is_empty()).then_some(mods.as_slice()),
|
|
4877
|
+
)
|
|
4878
|
+
.map_err(|e| e.to_string())
|
|
4879
|
+
})
|
|
4880
|
+
.map_err(|e| web_err(&format!("pressing key '{key}'"), e))?;
|
|
4703
4881
|
Ok(())
|
|
4704
4882
|
}
|
|
4705
4883
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//! Visibility must tolerate a dialog disappearing after the existence probe.
|
|
2
|
+
use flowproof_driver::{AppDriver, ScopeQuery, UiaSelector};
|
|
3
|
+
|
|
4
|
+
#[test]
|
|
5
|
+
fn removed_element_is_not_visible_without_swallowing_browser_errors() {
|
|
6
|
+
if std::env::var("FLOWPROOF_E2E").as_deref() != Ok("1") {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
let _guard = flowproof_adapters::SharedBrowserGuard::new();
|
|
10
|
+
let html = "<html><body><div id='dialog'>Receipt checked</div><div id='hidden' style='display:none'>Hidden</div><button id='close' onclick=\"document.getElementById('dialog').remove()\">Close</button><div id='scope'>Scope<button id='payment' style='display:none'>Payment</button></div><button id='reveal' onclick=\"document.getElementById('payment').style.display='block'\">Reveal</button></body></html>";
|
|
11
|
+
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("fixture listener");
|
|
12
|
+
let port = listener.local_addr().expect("fixture address").port();
|
|
13
|
+
std::thread::spawn(move || {
|
|
14
|
+
use std::io::{Read, Write};
|
|
15
|
+
for mut stream in listener.incoming().flatten() {
|
|
16
|
+
let mut request = [0; 2048];
|
|
17
|
+
let _ = stream.read(&mut request);
|
|
18
|
+
let _ = write!(stream, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", html.len(), html);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
let mut driver = flowproof_adapters::WebAppDriver::new().expect("Chrome launches");
|
|
22
|
+
driver
|
|
23
|
+
.launch(
|
|
24
|
+
&format!("http://127.0.0.1:{port}"),
|
|
25
|
+
"",
|
|
26
|
+
std::time::Duration::from_secs(30),
|
|
27
|
+
)
|
|
28
|
+
.expect("fixture loads");
|
|
29
|
+
let scoped = UiaSelector {
|
|
30
|
+
scope: Some(ScopeQuery {
|
|
31
|
+
container: "css:#scope".into(),
|
|
32
|
+
anchor: "Scope".into(),
|
|
33
|
+
inner_css: Some("#payment".into()),
|
|
34
|
+
..Default::default()
|
|
35
|
+
}),
|
|
36
|
+
..Default::default()
|
|
37
|
+
};
|
|
38
|
+
assert!(driver
|
|
39
|
+
.actionability_gate(&scoped)
|
|
40
|
+
.expect("hidden target waits")
|
|
41
|
+
.is_some());
|
|
42
|
+
driver
|
|
43
|
+
.invoke(&UiaSelector::css("#reveal"))
|
|
44
|
+
.expect("reveal target");
|
|
45
|
+
assert!(driver
|
|
46
|
+
.actionability_gate(&scoped)
|
|
47
|
+
.expect("visible target ready")
|
|
48
|
+
.is_none());
|
|
49
|
+
let dialog = UiaSelector::css("#dialog");
|
|
50
|
+
assert!(driver.element_exists(&dialog).expect("dialog existence"));
|
|
51
|
+
assert_eq!(
|
|
52
|
+
driver.element_visible(&dialog).expect("visible dialog"),
|
|
53
|
+
Some(true)
|
|
54
|
+
);
|
|
55
|
+
assert_eq!(
|
|
56
|
+
driver
|
|
57
|
+
.element_visible(&UiaSelector::css("#hidden"))
|
|
58
|
+
.expect("hidden element visibility"),
|
|
59
|
+
Some(false)
|
|
60
|
+
);
|
|
61
|
+
driver
|
|
62
|
+
.invoke(&UiaSelector::css("#close"))
|
|
63
|
+
.expect("close dialog");
|
|
64
|
+
assert_eq!(
|
|
65
|
+
driver
|
|
66
|
+
.element_visible(&dialog)
|
|
67
|
+
.expect("removed element visibility"),
|
|
68
|
+
Some(false)
|
|
69
|
+
);
|
|
70
|
+
assert_eq!(
|
|
71
|
+
driver
|
|
72
|
+
.element_visible(&UiaSelector::css("#never-present"))
|
|
73
|
+
.expect("absent element visibility"),
|
|
74
|
+
Some(false)
|
|
75
|
+
);
|
|
76
|
+
assert!(driver.element_visible(&UiaSelector::css("[")).is_err());
|
|
77
|
+
}
|
|
@@ -65,7 +65,7 @@ when it already holds a value the step does not contradict.
|
|
|
65
65
|
submit button behind mandatory fields - include the actions that supply it, then the \
|
|
66
66
|
action that reaches the goal.
|
|
67
67
|
- Respond with ONLY JSON, no prose, no code fences.
|
|
68
|
-
- The JSON action is one of: \"click\", \"click_at\", \"drag\", \"type_text\", \
|
|
68
|
+
- The JSON action is one of: \"click\", \"double_click\", \"click_at\", \"drag\", \"type_text\", \
|
|
69
69
|
\"assert_text\", \"capture_text\", \"capture_count\", \"type_captured\", \
|
|
70
70
|
\"select_option\", \"select_options\", \"scroll\", \"scroll_into_view\", \"press_key\", \"rule_step\", or \
|
|
71
71
|
\"capture_ambiguity\".
|
|
@@ -745,14 +745,23 @@ fn ground_one(
|
|
|
745
745
|
)));
|
|
746
746
|
};
|
|
747
747
|
match authored.action.as_str() {
|
|
748
|
-
"click" => {
|
|
748
|
+
"click" | "double_click" => {
|
|
749
749
|
if !scene_token_is_actionable(scene, token) {
|
|
750
750
|
return Err("click target is readable but not actionable".into());
|
|
751
751
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
752
|
+
let label = scene_label(scene, token).unwrap_or_default();
|
|
753
|
+
Ok(vec![if authored.action == "double_click" {
|
|
754
|
+
ResolvedAction::DoubleClick {
|
|
755
|
+
target,
|
|
756
|
+
label,
|
|
757
|
+
dialog: None,
|
|
758
|
+
}
|
|
759
|
+
} else {
|
|
760
|
+
ResolvedAction::Press {
|
|
761
|
+
target,
|
|
762
|
+
label,
|
|
763
|
+
dialog: None,
|
|
764
|
+
}
|
|
756
765
|
}])
|
|
757
766
|
}
|
|
758
767
|
"click_at" => {
|
|
@@ -2248,6 +2257,15 @@ mod tests {
|
|
|
2248
2257
|
#[test]
|
|
2249
2258
|
fn human_language_primitives_ground_without_rules_in_the_input() {
|
|
2250
2259
|
let cases = [
|
|
2260
|
+
(
|
|
2261
|
+
"Double-click task 1 to activate its editor",
|
|
2262
|
+
r#"{"action":"double_click","target":"css:#task-1"}"#,
|
|
2263
|
+
ResolvedAction::DoubleClick {
|
|
2264
|
+
target: Target::css("#task-1"),
|
|
2265
|
+
label: "task 1".into(),
|
|
2266
|
+
dialog: None,
|
|
2267
|
+
},
|
|
2268
|
+
),
|
|
2251
2269
|
(
|
|
2252
2270
|
"Drag task 1 into the todo drop area",
|
|
2253
2271
|
r#"{"action":"drag","target":"css:#task-1","onto":"css:#todo"}"#,
|
|
@@ -20,8 +20,10 @@ chrono = { workspace = true }
|
|
|
20
20
|
clap = { workspace = true }
|
|
21
21
|
# Cross-platform Ctrl-C handling for the long-lived `capture` listener, so a
|
|
22
22
|
# deliberate interrupt exits 0 cleanly on Windows and Unix alike. Light: no
|
|
23
|
-
# async runtime, just a signal handler.
|
|
24
|
-
|
|
23
|
+
# async runtime, just a signal handler. `termination` additionally catches
|
|
24
|
+
# SIGTERM/SIGHUP on Unix (a plain `kill` or a CI timeout, not just Ctrl-C) -
|
|
25
|
+
# see `run_cli`'s shared-browser signal handler.
|
|
26
|
+
ctrlc = { version = "3", features = ["termination"] }
|
|
25
27
|
# `flowproof config`'s platform config directory (plans/001-credential-config.md,
|
|
26
28
|
# "Where the file lives"): a single-purpose lookup, no multi-app namespacing
|
|
27
29
|
# to justify `directories`' extra structure.
|