flowproof 0.23.6__tar.gz → 0.23.7__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.6 → flowproof-0.23.7}/Cargo.lock +7 -7
- {flowproof-0.23.6 → flowproof-0.23.7}/Cargo.toml +1 -1
- {flowproof-0.23.6 → flowproof-0.23.7}/PKG-INFO +1 -1
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/web.rs +242 -15
- flowproof-0.23.7/crates/flowproof-adapters/tests/list_scene_e2e.rs +49 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/author.rs +161 -5
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/clarify.rs +5 -5
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/lib.rs +10 -1
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/llm.rs +10 -2
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/recorder.rs +72 -6
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/tests/model_client_http.rs +2 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/config.rs +52 -1
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/lib.rs +62 -5
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/config_seed_e2e.rs +56 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/doctor_ai_e2e.rs +54 -17
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/examples_resolve.rs +38 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/llm_author_e2e.rs +4 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/notepad_author_e2e.rs +1 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/mock.rs +4 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/src/lib.rs +31 -1
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/tests/replay_calc.rs +89 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/__init__.py +1 -1
- {flowproof-0.23.6 → flowproof-0.23.7}/pyproject.toml +1 -1
- {flowproof-0.23.6 → flowproof-0.23.7}/README.md +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/Cargo.toml +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/agent_proxy.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/agent_runner.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_linux.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/audit.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/capture.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/env_block.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/filters.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/identity.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/logon.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/netevents.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/run.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/spawn.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/wfp.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/fiori.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/fs_observe.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/lib.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/mcp_core.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/mcp_http.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/mcp_stdio.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/sap_com.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/vision.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/a11y_capture.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/drag_spike.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/fs_observation_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/grid_scene_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/network_idle_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/shared_browser_shutdown_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/side_effect_http_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/static_area_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/visibility_detach_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/windows_containment_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/Cargo.toml +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/agent_steps.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/doc_author.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/doc_formats/hp_alm.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/doc_formats.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/draft_assembly.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/heal.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/repair.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/rules.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/spec.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/Cargo.toml +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/skills/flowproof-config/SKILL.md +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/agent_flow.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/capture.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/doctor.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/main.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/suite.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/suite_checkpoint.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/suite_inputs.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/suite_journal.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/update_check.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/a11y_selector_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/agent_conversation_anthropic_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/agent_conversation_streaming_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/agent_flow_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/agent_url_record_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/api_pipeline.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/audit_record_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/calc_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/clock_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/config_cli_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/config_skill_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/cookie_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/doctor_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/documented_flags.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/egress_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_arg_matchers.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_audit_since.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_call_order.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_capability_regression.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_control_verdict.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_mcp_kill.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_no_tool_call.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_reply_contains.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_side_effect.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/fiori_field_commit_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/grid_cell_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/iframe_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/mcp_http_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/mcp_stdio_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/notepad_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/page_title_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/pinned_random_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/sap_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/sap_pipeline.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/sap_sim_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/scoped_container_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/select_missing_option_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/sigterm_shared_browser_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/skip_unless_env.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_checkpoint.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_env_from.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_flow_isolation.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_missing_trace.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_policy.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/support/sap_simulator.py +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/visibility_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/vision_pipeline.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/web_e2e.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/Cargo.toml +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/app.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/backend.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/gdi.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/lib.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/oob.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/recording.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/redact.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/surface.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/visual.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/window.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-python/Cargo.toml +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-python/src/lib.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/Cargo.toml +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/src/report.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/src/runrecord.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/Cargo.toml +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/schema/cassette-v1.schema.json +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/schema/side-effect-v1.schema.json +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/schema/trace-v1.schema.json +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/captures.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/cassette.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/cassette_diff.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/egress.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/format.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/lib.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/secret.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/secret_scan.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/side_effect.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/substitution.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/toolcalls.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/cassette_conformance.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/fixtures/multi.trace.jsonl +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/fixtures/sample.trace.jsonl +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/schema_conformance.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/side_effect_conformance.rs +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/cli.py +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/flow.py +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/mcp_server.py +0 -0
- {flowproof-0.23.6 → flowproof-0.23.7}/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.7"
|
|
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.7"
|
|
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.7"
|
|
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.7"
|
|
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.7"
|
|
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.7"
|
|
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.7"
|
|
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.7"
|
|
26
26
|
edition = "2021"
|
|
27
27
|
license = "Apache-2.0"
|
|
28
28
|
repository = "https://github.com/automators-com/flowproof"
|
|
@@ -1040,6 +1040,19 @@ fn launch_browser(extra_args: &[String]) -> Result<Browser, AdapterError> {
|
|
|
1040
1040
|
.map_err(|e| AdapterError::Web(launch_failure_message(&e.to_string(), headed)))
|
|
1041
1041
|
}
|
|
1042
1042
|
|
|
1043
|
+
/// Reuse Chromium's startup page in a privately owned browser. Never use
|
|
1044
|
+
/// this for shared contexts: each flow there must keep its isolated storage.
|
|
1045
|
+
fn private_flow_tab(browser: &Browser) -> anyhow::Result<Arc<Tab>> {
|
|
1046
|
+
// This API is deprecated in favor of new_tab(), but waiting for and reusing
|
|
1047
|
+
// the startup target is intentional: new_tab() leaves an extra visible tab.
|
|
1048
|
+
#[allow(deprecated)]
|
|
1049
|
+
let initial = browser.wait_for_initial_tab()?;
|
|
1050
|
+
match initial.get_url().as_str() {
|
|
1051
|
+
"about:blank" | "chrome://newtab/" | "chrome://new-tab-page/" => Ok(initial),
|
|
1052
|
+
_ => browser.new_tab(),
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1043
1056
|
/// A private per-launch downloads directory when the flow didn't pin one via
|
|
1044
1057
|
/// `browser.downloads_dir`. Unique per launch (pid + a monotonic counter) so
|
|
1045
1058
|
/// two flows sharing the same shared-browser process never race over the
|
|
@@ -1314,6 +1327,7 @@ pub struct WebAppDriver {
|
|
|
1314
1327
|
/// Active request identities, maintained by CDP. Redirects reuse their
|
|
1315
1328
|
/// request ID and must not leave a phantom in-flight request behind.
|
|
1316
1329
|
network_inflight: Arc<NetworkActivity>,
|
|
1330
|
+
observed_collections: Vec<serde_json::Value>,
|
|
1317
1331
|
}
|
|
1318
1332
|
|
|
1319
1333
|
/// Shared state between the driver and the flow-wide dialog listener.
|
|
@@ -1515,6 +1529,7 @@ impl WebAppDriver {
|
|
|
1515
1529
|
downloads_dir: None,
|
|
1516
1530
|
claimed_downloads: Default::default(),
|
|
1517
1531
|
network_inflight: Default::default(),
|
|
1532
|
+
observed_collections: Vec::new(),
|
|
1518
1533
|
});
|
|
1519
1534
|
}
|
|
1520
1535
|
let browser = shared_browser()?;
|
|
@@ -1535,6 +1550,7 @@ impl WebAppDriver {
|
|
|
1535
1550
|
downloads_dir: None,
|
|
1536
1551
|
claimed_downloads: Default::default(),
|
|
1537
1552
|
network_inflight: Default::default(),
|
|
1553
|
+
observed_collections: Vec::new(),
|
|
1538
1554
|
})
|
|
1539
1555
|
}
|
|
1540
1556
|
|
|
@@ -2144,6 +2160,55 @@ fn is_transport_fault(message: &str) -> bool {
|
|
|
2144
2160
|
|| m.contains("unable to make method calls")
|
|
2145
2161
|
}
|
|
2146
2162
|
|
|
2163
|
+
/// How long a single CDP call is allowed to wait for a response before this
|
|
2164
|
+
/// side gives up on it (issue #616). The vendored `headless_chrome` fork
|
|
2165
|
+
/// bounds that wait with `idle_browser_timeout` - but that same field also
|
|
2166
|
+
/// sets the reader thread's own shutdown-poll interval and a separate
|
|
2167
|
+
/// browser-level idle-connection timer (three different jobs, one shared
|
|
2168
|
+
/// knob), and flowproof sets it to 300s for those other purposes. Lowering
|
|
2169
|
+
/// it directly would risk shortening the other two as a side effect.
|
|
2170
|
+
/// Wrapping the call from this side instead - the same fix browser-use
|
|
2171
|
+
/// applied for the identical failure shape in `_cdp_timeout.py` - needs no
|
|
2172
|
+
/// change to the vendored fork at all. 30s is well above every attempt's
|
|
2173
|
+
/// normal total duration observed live (~35-50s for an entire multi-step
|
|
2174
|
+
/// flow, not one call) and far below the 300s a stuck call currently costs.
|
|
2175
|
+
const CDP_CALL_TIMEOUT: Duration = Duration::from_secs(30);
|
|
2176
|
+
|
|
2177
|
+
/// Waits at most [`CDP_CALL_TIMEOUT`] for `f`, running it on its own thread
|
|
2178
|
+
/// since Rust has no way to cancel a blocking call already in flight (`f`
|
|
2179
|
+
/// itself is still bound by the vendored fork's much longer wait and keeps
|
|
2180
|
+
/// running to completion on that thread regardless - this only stops THIS
|
|
2181
|
+
/// caller from waiting on it). Confirmed live: about a third of attempts on
|
|
2182
|
+
/// one spec spent 3-10 minutes on a single stuck call instead of the normal
|
|
2183
|
+
/// ~35-50s for the whole flow; retrying immediately instead of waiting out
|
|
2184
|
+
/// that stall gives a fresh attempt a chance to land well within the same
|
|
2185
|
+
/// wall-clock budget a client would tolerate.
|
|
2186
|
+
///
|
|
2187
|
+
/// The timeout's own error text matches `is_transport_fault`, so a caller
|
|
2188
|
+
/// that already retries on transport faults (`frame_act`, `probe_frame`)
|
|
2189
|
+
/// treats a fast timeout exactly like the slow error it now stands in for -
|
|
2190
|
+
/// no separate handling needed at the call site.
|
|
2191
|
+
fn with_cdp_timeout<T: Send + 'static>(
|
|
2192
|
+
f: impl FnOnce() -> Result<T, String> + Send + 'static,
|
|
2193
|
+
) -> Result<T, String> {
|
|
2194
|
+
with_cdp_timeout_after(CDP_CALL_TIMEOUT, f)
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
/// The actual wait, with the deadline injectable so tests can prove the
|
|
2198
|
+
/// timeout fires (and what it produces) in milliseconds instead of at the
|
|
2199
|
+
/// real 30s production value.
|
|
2200
|
+
fn with_cdp_timeout_after<T: Send + 'static>(
|
|
2201
|
+
deadline: Duration,
|
|
2202
|
+
f: impl FnOnce() -> Result<T, String> + Send + 'static,
|
|
2203
|
+
) -> Result<T, String> {
|
|
2204
|
+
let (tx, rx) = std::sync::mpsc::channel();
|
|
2205
|
+
std::thread::spawn(move || {
|
|
2206
|
+
let _ = tx.send(f());
|
|
2207
|
+
});
|
|
2208
|
+
rx.recv_timeout(deadline)
|
|
2209
|
+
.unwrap_or_else(|_| Err("the event waited for never came".to_string()))
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2147
2212
|
/// Attempts allowed beyond the first for a transport-faulting operation.
|
|
2148
2213
|
/// Was 1 (a single fixed ~300ms retry) until issue #608: a clean live
|
|
2149
2214
|
/// re-verification of `medium-02-change-info-record-readonly.flow.yaml`
|
|
@@ -2304,6 +2369,57 @@ mod transport_retry_tests {
|
|
|
2304
2369
|
}
|
|
2305
2370
|
}
|
|
2306
2371
|
|
|
2372
|
+
#[cfg(test)]
|
|
2373
|
+
mod cdp_timeout_tests {
|
|
2374
|
+
use super::with_cdp_timeout_after;
|
|
2375
|
+
use std::time::Duration;
|
|
2376
|
+
|
|
2377
|
+
/// The common case: a call that returns well within the deadline is
|
|
2378
|
+
/// unaffected by the wrapper.
|
|
2379
|
+
#[test]
|
|
2380
|
+
fn a_fast_call_returns_its_own_result() {
|
|
2381
|
+
let result = with_cdp_timeout_after(Duration::from_millis(200), || Ok::<_, String>(42));
|
|
2382
|
+
assert_eq!(result, Ok(42));
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
/// issue #616: a call still stuck past the deadline must not block the
|
|
2386
|
+
/// caller - the timeout fires and hands back control, even though the
|
|
2387
|
+
/// spawned thread (standing in for the real CDP wait) is still running.
|
|
2388
|
+
#[test]
|
|
2389
|
+
fn a_call_still_stuck_past_the_deadline_times_out_instead_of_blocking() {
|
|
2390
|
+
let result = with_cdp_timeout_after(Duration::from_millis(50), || {
|
|
2391
|
+
std::thread::sleep(Duration::from_secs(5));
|
|
2392
|
+
Ok::<_, String>(())
|
|
2393
|
+
});
|
|
2394
|
+
assert_eq!(result, Err("the event waited for never came".to_string()));
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
/// The timeout's own error text must match `is_transport_fault`, so a
|
|
2398
|
+
/// caller that already retries on transport faults treats a fast
|
|
2399
|
+
/// timeout exactly like the slow error it now stands in for, with no
|
|
2400
|
+
/// separate handling needed at the call site.
|
|
2401
|
+
#[test]
|
|
2402
|
+
fn the_timeout_error_is_itself_a_recognized_transport_fault() {
|
|
2403
|
+
let result: Result<(), String> = with_cdp_timeout_after(Duration::from_millis(50), || {
|
|
2404
|
+
std::thread::sleep(Duration::from_secs(5));
|
|
2405
|
+
Ok(())
|
|
2406
|
+
});
|
|
2407
|
+
match result {
|
|
2408
|
+
Err(e) => assert!(super::is_transport_fault(&e)),
|
|
2409
|
+
Ok(()) => panic!("expected a timeout error"),
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
/// A genuine error from `f` (not a timeout) passes through unchanged -
|
|
2414
|
+
/// the wrapper must not turn every real failure into "timed out".
|
|
2415
|
+
#[test]
|
|
2416
|
+
fn a_real_error_from_f_is_not_reported_as_a_timeout() {
|
|
2417
|
+
let result: Result<(), String> =
|
|
2418
|
+
with_cdp_timeout_after(Duration::from_millis(200), || Err("no_element".to_string()));
|
|
2419
|
+
assert_eq!(result, Err("no_element".to_string()));
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2307
2423
|
/// How a [`UiaSelector`] resolves on a page: a CSS selector or a text
|
|
2308
2424
|
/// anchor, optionally narrowed to the nth match (1-based).
|
|
2309
2425
|
struct WebLocator {
|
|
@@ -2584,11 +2700,14 @@ impl WebAppDriver {
|
|
|
2584
2700
|
// uses for every field inside an iframe) killed the whole flow. A
|
|
2585
2701
|
// single retry was also not enough on its own (issue #608) - this
|
|
2586
2702
|
// is the call site that failed twice in a row live.
|
|
2703
|
+
let tab = self.tab().map(Arc::clone).map_err(|e| e.to_string());
|
|
2587
2704
|
let status = retry_on_transport_fault(|| {
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2705
|
+
let tab = tab.clone()?;
|
|
2706
|
+
let call = call.clone();
|
|
2707
|
+
// issue #616: this call can stall for minutes on a dead
|
|
2708
|
+
// connection before the vendored transport's own much larger
|
|
2709
|
+
// timeout gives up - bound the wait from this side instead.
|
|
2710
|
+
with_cdp_timeout(move || tab.evaluate(&call, false).map_err(|e| e.to_string()))
|
|
2592
2711
|
})
|
|
2593
2712
|
.map_err(|e| web_err("acting inside an iframe", e))?
|
|
2594
2713
|
.value
|
|
@@ -2897,6 +3016,7 @@ impl AppDriver for WebAppDriver {
|
|
|
2897
3016
|
_window_name: &str,
|
|
2898
3017
|
_timeout: Duration,
|
|
2899
3018
|
) -> Result<(), DriverError> {
|
|
3019
|
+
self.observed_collections.clear();
|
|
2900
3020
|
let staged_browser = self.staged_browser.take();
|
|
2901
3021
|
// Extra Chrome flags only apply at process start: swap in a
|
|
2902
3022
|
// PRIVATE browser for this flow (a plain tab on it is already
|
|
@@ -2925,7 +3045,7 @@ impl AppDriver for WebAppDriver {
|
|
|
2925
3045
|
for_tab: None,
|
|
2926
3046
|
hidden: None,
|
|
2927
3047
|
}),
|
|
2928
|
-
None => self.browser
|
|
3048
|
+
None => private_flow_tab(&self.browser),
|
|
2929
3049
|
}
|
|
2930
3050
|
.map_err(|e| web_err("opening tab", e))?;
|
|
2931
3051
|
// A visible flow is the only Chrome window the user should have to
|
|
@@ -4312,11 +4432,13 @@ impl AppDriver for WebAppDriver {
|
|
|
4312
4432
|
// the tab directly, with no element to re-resolve, so it never went
|
|
4313
4433
|
// through that retry at all - any transient CDP hiccup here killed
|
|
4314
4434
|
// the whole flow on the first blip, with zero chance to recover.
|
|
4435
|
+
let tab = self.tab().map(Arc::clone).map_err(|e| e.to_string());
|
|
4315
4436
|
let status = retry_on_transport_fault(|| {
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4437
|
+
let tab = tab.clone()?;
|
|
4438
|
+
let call = call.clone();
|
|
4439
|
+
// issue #616: bound this call's wait from the caller's side
|
|
4440
|
+
// instead of the vendored transport's much larger one.
|
|
4441
|
+
with_cdp_timeout(move || tab.evaluate(&call, false).map_err(|e| e.to_string()))
|
|
4320
4442
|
})
|
|
4321
4443
|
.map_err(|e| web_err("probing an iframe", e))?
|
|
4322
4444
|
.value
|
|
@@ -5124,6 +5246,14 @@ impl AppDriver for WebAppDriver {
|
|
|
5124
5246
|
const ordered = all.filter(priorityField).concat(all.filter(interactive),
|
|
5125
5247
|
all.filter(el => !interactive(el) && readableLeaf(el))
|
|
5126
5248
|
);
|
|
5249
|
+
const hitTestableTick = el => {
|
|
5250
|
+
if (!el.matches('input[type=checkbox], input[type=radio]')) return false;
|
|
5251
|
+
const r = el.getBoundingClientRect();
|
|
5252
|
+
// Transparent native controls often overlay a drawn checkbox.
|
|
5253
|
+
// Include only controls that really receive the pointer here.
|
|
5254
|
+
return r.width > 1 && r.height > 1 &&
|
|
5255
|
+
document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2) === el;
|
|
5256
|
+
};
|
|
5127
5257
|
const seen = new Set();
|
|
5128
5258
|
const chosen = ordered.filter(el => {
|
|
5129
5259
|
const r = el.getBoundingClientRect();
|
|
@@ -5133,7 +5263,7 @@ impl AppDriver for WebAppDriver {
|
|
|
5133
5263
|
// even when the field starts below the fold, and every web
|
|
5134
5264
|
// action already scrolls its target into view before acting.
|
|
5135
5265
|
const rendered = style.display !== 'none' && style.visibility !== 'hidden' &&
|
|
5136
|
-
Number(style.opacity) > 0 && r.width > 0 && r.height > 0;
|
|
5266
|
+
(Number(style.opacity) > 0 || hitTestableTick(el)) && r.width > 0 && r.height > 0;
|
|
5137
5267
|
if (!rendered || seen.has(el)) return false;
|
|
5138
5268
|
seen.add(el);
|
|
5139
5269
|
return true;
|
|
@@ -5208,6 +5338,22 @@ impl AppDriver for WebAppDriver {
|
|
|
5208
5338
|
// final cell without naming one current row. Represent those
|
|
5209
5339
|
// collection identities directly so the model can ground the
|
|
5210
5340
|
// intent without inventing a selector from today's DOM.
|
|
5341
|
+
// Keep empty list identities available for assertions. A model
|
|
5342
|
+
// must not invent a selector just because the last item vanished.
|
|
5343
|
+
for (const list of document.querySelectorAll('ul, ol, [role="list"]')) {
|
|
5344
|
+
const listCss = semanticCss(list);
|
|
5345
|
+
if (!listCss) continue;
|
|
5346
|
+
const itemCss = listCss + ' > :is(li, [role="listitem"])';
|
|
5347
|
+
entries.push({
|
|
5348
|
+
target: 'css:' + itemCss,
|
|
5349
|
+
css: itemCss,
|
|
5350
|
+
tag: 'collection',
|
|
5351
|
+
actionable: false,
|
|
5352
|
+
count: list.querySelectorAll(':scope > :is(li, [role="listitem"])').length,
|
|
5353
|
+
label: 'items in ' + (list.getAttribute('aria-label') || listCss),
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
5356
|
+
|
|
5211
5357
|
for (const table of document.querySelectorAll('table')) {
|
|
5212
5358
|
const tableCss = semanticCss(table);
|
|
5213
5359
|
if (!tableCss) continue;
|
|
@@ -5304,11 +5450,16 @@ impl AppDriver for WebAppDriver {
|
|
|
5304
5450
|
});
|
|
5305
5451
|
})()
|
|
5306
5452
|
"#;
|
|
5453
|
+
let tab = Arc::clone(self.tab()?);
|
|
5307
5454
|
let sample = || {
|
|
5308
|
-
let
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5455
|
+
let tab = Arc::clone(&tab);
|
|
5456
|
+
// Replay now asks for a settled scene before judging text after
|
|
5457
|
+
// navigation. Keep that defensive wait from opening another
|
|
5458
|
+
// unbounded direct-CDP path: if this read stalls, the assertion
|
|
5459
|
+
// falls back to its ordinary recorded polling window.
|
|
5460
|
+
let value =
|
|
5461
|
+
with_cdp_timeout(move || tab.evaluate(SCENE_JS, false).map_err(|e| e.to_string()))
|
|
5462
|
+
.map_err(|e| web_err("evaluating scene script", e))?;
|
|
5312
5463
|
let raw = value
|
|
5313
5464
|
.value
|
|
5314
5465
|
.and_then(|v| v.as_str().map(str::to_string))
|
|
@@ -5320,13 +5471,50 @@ impl AppDriver for WebAppDriver {
|
|
|
5320
5471
|
entries: parsed["entries"].as_array().cloned().unwrap_or_default(),
|
|
5321
5472
|
})
|
|
5322
5473
|
};
|
|
5323
|
-
let entries = settled_scene(
|
|
5474
|
+
let mut entries = settled_scene(
|
|
5324
5475
|
sample,
|
|
5325
5476
|
|| std::thread::sleep(SCENE_SETTLE_INTERVAL),
|
|
5326
5477
|
|| self.network_idle(),
|
|
5327
5478
|
SCENE_SETTLE_ROUNDS,
|
|
5328
5479
|
SCENE_SETTLE_BUSY_ROUNDS,
|
|
5329
5480
|
)?;
|
|
5481
|
+
for entry in entries.iter().filter(|e| e["tag"] == "collection") {
|
|
5482
|
+
if !self
|
|
5483
|
+
.observed_collections
|
|
5484
|
+
.iter()
|
|
5485
|
+
.any(|old| old["target"] == entry["target"])
|
|
5486
|
+
&& self.observed_collections.len() < 50
|
|
5487
|
+
{
|
|
5488
|
+
self.observed_collections.push(entry.clone());
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5491
|
+
// Some apps remove the whole list when its last item is deleted.
|
|
5492
|
+
// Retain only collection identities seen in this browser session,
|
|
5493
|
+
// and re-count the DOM now; never infer absence from missing inventory.
|
|
5494
|
+
let missing: Vec<_> = self
|
|
5495
|
+
.observed_collections
|
|
5496
|
+
.iter()
|
|
5497
|
+
.filter(|old| !entries.iter().any(|e| e["target"] == old["target"]))
|
|
5498
|
+
.cloned()
|
|
5499
|
+
.collect();
|
|
5500
|
+
if !missing.is_empty() {
|
|
5501
|
+
let input =
|
|
5502
|
+
serde_json::to_string(&missing).map_err(|e| DriverError::Browser(e.to_string()))?;
|
|
5503
|
+
let script = format!("JSON.stringify(({input}).map(e => ({{...e, count: document.querySelectorAll(e.css).length, observed_before: true}})))");
|
|
5504
|
+
let value = self
|
|
5505
|
+
.tab()?
|
|
5506
|
+
.evaluate(&script, false)
|
|
5507
|
+
.map_err(|e| web_err("counting previously observed lists", e))?;
|
|
5508
|
+
let previous: Vec<serde_json::Value> = serde_json::from_str(
|
|
5509
|
+
value
|
|
5510
|
+
.value
|
|
5511
|
+
.as_ref()
|
|
5512
|
+
.and_then(|v| v.as_str())
|
|
5513
|
+
.ok_or_else(|| DriverError::Browser("collection counts missing".into()))?,
|
|
5514
|
+
)
|
|
5515
|
+
.map_err(|e| DriverError::Browser(format!("collection counts: {e}")))?;
|
|
5516
|
+
entries.extend(previous);
|
|
5517
|
+
}
|
|
5330
5518
|
let json = serde_json::to_string(&entries)
|
|
5331
5519
|
.map_err(|e| DriverError::Browser(format!("re-serialising scene: {e}")))?;
|
|
5332
5520
|
Ok(Some(json))
|
|
@@ -5913,3 +6101,42 @@ mod tests {
|
|
|
5913
6101
|
assert_eq!(got, vec![&a, &b]);
|
|
5914
6102
|
}
|
|
5915
6103
|
}
|
|
6104
|
+
|
|
6105
|
+
#[cfg(test)]
|
|
6106
|
+
mod startup_tab_tests {
|
|
6107
|
+
#[test]
|
|
6108
|
+
fn private_browser_reuses_startup_tab_without_replacing_a_loaded_page() {
|
|
6109
|
+
if std::env::var("FLOWPROOF_E2E").as_deref() != Ok("1") {
|
|
6110
|
+
eprintln!("set FLOWPROOF_E2E=1 to run the real browser check");
|
|
6111
|
+
return;
|
|
6112
|
+
}
|
|
6113
|
+
let browser = super::Browser::new(
|
|
6114
|
+
super::launch_options_for(&[], true).expect("valid launch options"),
|
|
6115
|
+
)
|
|
6116
|
+
.expect("browser starts");
|
|
6117
|
+
let first = super::private_flow_tab(&browser).expect("flow tab opens");
|
|
6118
|
+
assert_eq!(
|
|
6119
|
+
browser
|
|
6120
|
+
.get_tabs()
|
|
6121
|
+
.lock()
|
|
6122
|
+
.expect("tab registry is available")
|
|
6123
|
+
.len(),
|
|
6124
|
+
1
|
|
6125
|
+
);
|
|
6126
|
+
first
|
|
6127
|
+
.navigate_to("data:text/html,<title>Demo</title>Ready")
|
|
6128
|
+
.expect("demo page loads");
|
|
6129
|
+
first.wait_until_navigated().expect("demo page loads");
|
|
6130
|
+
let second = super::private_flow_tab(&browser).expect("flow tab opens");
|
|
6131
|
+
assert_ne!(first.get_target_id(), second.get_target_id());
|
|
6132
|
+
assert!(first.get_url().starts_with("data:text/html,"));
|
|
6133
|
+
assert_eq!(
|
|
6134
|
+
browser
|
|
6135
|
+
.get_tabs()
|
|
6136
|
+
.lock()
|
|
6137
|
+
.expect("tab registry is available")
|
|
6138
|
+
.len(),
|
|
6139
|
+
2
|
|
6140
|
+
);
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use flowproof_driver::AppDriver;
|
|
2
|
+
|
|
3
|
+
#[test]
|
|
4
|
+
fn empty_and_populated_lists_offer_grounded_collection_targets() {
|
|
5
|
+
if std::env::var("FLOWPROOF_E2E").as_deref() != Ok("1") {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
let _guard = flowproof_adapters::SharedBrowserGuard::new();
|
|
9
|
+
let mut driver = flowproof_adapters::WebAppDriver::new().expect("browser starts");
|
|
10
|
+
driver
|
|
11
|
+
.launch(
|
|
12
|
+
"data:text/html,<input id='tick' type='checkbox' aria-label='Toggle task' style='opacity:0;width:30px;height:30px'><input id='hidden' type='checkbox' style='display:none'><button id='remove' onclick='document.getElementById("steps").remove()'>Remove</button><ul id='tasks'></ul><ol id='steps'><li>One</li><li>Two</li></ol>",
|
|
13
|
+
"",
|
|
14
|
+
std::time::Duration::from_secs(10),
|
|
15
|
+
)
|
|
16
|
+
.expect("fixture opens");
|
|
17
|
+
let scene = driver.scene().expect("scene reads").expect("scene exists");
|
|
18
|
+
let entries: Vec<serde_json::Value> = serde_json::from_str(&scene).expect("scene JSON");
|
|
19
|
+
let tick = entries
|
|
20
|
+
.iter()
|
|
21
|
+
.find(|e| e["target"] == "css:#tick")
|
|
22
|
+
.expect("transparent hit-testable checkbox offered");
|
|
23
|
+
assert_eq!(tick["actionable"], true);
|
|
24
|
+
assert_eq!(tick["checked"], false);
|
|
25
|
+
assert!(!entries.iter().any(|e| e["target"] == "css:#hidden"));
|
|
26
|
+
for (id, count) in [("tasks", 0), ("steps", 2)] {
|
|
27
|
+
let token = format!("css:#{id} > :is(li, [role=\"listitem\"])");
|
|
28
|
+
let entry = entries
|
|
29
|
+
.iter()
|
|
30
|
+
.find(|e| e["target"] == token)
|
|
31
|
+
.expect("collection target exists even when empty");
|
|
32
|
+
assert_eq!(entry["count"], count);
|
|
33
|
+
assert_eq!(entry["actionable"], false);
|
|
34
|
+
}
|
|
35
|
+
driver
|
|
36
|
+
.invoke(&flowproof_driver::UiaSelector::css("#remove"))
|
|
37
|
+
.expect("remove populated list");
|
|
38
|
+
let scene = driver
|
|
39
|
+
.scene()
|
|
40
|
+
.expect("scene after removal")
|
|
41
|
+
.expect("scene exists");
|
|
42
|
+
let entries: Vec<serde_json::Value> = serde_json::from_str(&scene).expect("scene JSON");
|
|
43
|
+
let previous = entries
|
|
44
|
+
.iter()
|
|
45
|
+
.find(|e| e["target"] == "css:#steps > :is(li, [role=\"listitem\"])")
|
|
46
|
+
.expect("previous list retained");
|
|
47
|
+
assert_eq!(previous["count"], 0);
|
|
48
|
+
assert_eq!(previous["observed_before"], true);
|
|
49
|
+
}
|