Skip to main content

devicerail_protocol/
lib.rs

1mod action;
2mod device;
3mod event;
4#[cfg(feature = "fixtures")]
5pub mod fixtures;
6mod handshake;
7mod manual;
8mod methods;
9mod rpc;
10#[cfg(feature = "schema")]
11pub mod schema;
12mod stream;
13mod ui;
14mod wire_integer;
15
16pub use action::{
17    ActionCall, ActionDefinition, ActionProtection, ActionResult, DeviceExecuteParams,
18    RecordedActionCall,
19};
20pub use device::{
21    AssetRef, DeviceId, DeviceInfo, DeviceSelectParams, DeviceSelectResult, DevicesListResult,
22    Observation, Platform, ScreenshotOmissionReason, Viewport,
23};
24pub use event::{
25    ActionOutcome, ErrorInfo, EventId, EventSequence, MAX_VERDICT_EVIDENCE_REFERENCES,
26    MAX_VERDICT_SUMMARY_LENGTH, MediaFrame, MediaStreamId, MediaStreamInfo, MediaStreamKind,
27    SessionExport, SessionId, SessionInfo, SessionOutcome, SessionState, TestEvent,
28    TestEventPayload, Verdict, VerdictStatus, VerdictValidationError,
29};
30pub use handshake::{
31    FeatureNegotiationError, FeatureOffer, FeatureSelection, HelloParams, HelloResult,
32    PROTOCOL_VERSION, PeerInfo, ProtocolIncompatibilityReason, ProtocolNegotiationError,
33    ProtocolOffer, ProtocolRange, ProtocolSelection, ProtocolVersion, TransportInfo, feature,
34    negotiate_features, negotiate_protocol, supported_protocol_offer,
35};
36pub use manual::{
37    MANUAL_RECORDING_VERSION, ManualActionArguments, ManualActionStep, ManualRecording,
38};
39pub use methods::{
40    DeviceCapabilitiesResult, DeviceConnectResult, DeviceDisconnectResult, DeviceExecuteResult,
41    DeviceObserveResult, EventsClearResult, EventsListParams, EventsListResult,
42    MAX_EVENTS_LIST_PAGE_SIZE, MediaStreamCaptureParams, MediaStreamCaptureResult,
43    MediaStreamEndParams, MediaStreamEndResult, MediaStreamStartParams, MediaStreamStartResult,
44    SessionCurrentResult, SessionEndParams, SessionEndResult, SessionExportParams,
45    SessionExportResult, SessionStartResult, SessionTargetParams, SessionsListResult,
46    SystemDescribeResult, UiSnapshotGetParams, UiSnapshotGetResult, VerdictRecordParams,
47    VerdictRecordResult,
48};
49pub use rpc::{
50    JsonRpcVersion, MAX_SAFE_INTEGER_ID, RequestCancelParams, RequestCancelResult,
51    RequestCancelStatus, RequestTimeoutMs, RpcError, RpcId, RpcParams, RpcRequest, RpcResponse,
52};
53pub use stream::{
54    EventStreamCursor, EventStreamEndpoint, EventStreamEpoch, EventStreamOriginPolicy,
55    EventSubscriptionId, EventsStreamEventMethod, EventsStreamEventNotification,
56    EventsStreamEventParams, EventsStreamOpenParams, EventsStreamOpenResult,
57    EventsStreamTerminalMethod, EventsStreamTerminalNotification, EventsStreamTerminalParams,
58    EventsStreamTermination, EventsSubscribeParams, EventsSubscribeResult, RpcServerMessage,
59    RpcServerNotification,
60};
61pub use ui::{
62    ActionExecution, CLEAR_ELEMENT_ACTION, ClearElementArguments, ClearElementResult,
63    CoordinateFallbackReason, ElementActionOutput, ElementSelector, ElementTarget,
64    FIND_ELEMENT_ACTION, FindElementArguments, FindElementResult, MAX_UI_IDENTIFIER_LENGTH,
65    MAX_UI_ROLE_LENGTH, MAX_UI_SNAPSHOT_BYTES, MAX_UI_SNAPSHOT_NODES, MAX_UI_TEXT_LENGTH,
66    SEMANTIC_ACTION_NAMES, SET_ELEMENT_VALUE_ACTION, SetElementValueArguments,
67    SetElementValueResult, TAP_ELEMENT_ACTION, TapElementArguments, TapElementResult, TextMatch,
68    TextMatchMode, UI_SNAPSHOT_FORMAT_VERSION, UI_SNAPSHOT_MEDIA_TYPE, UiContextKind, UiContextRef,
69    UiContextSelector, UiContractError, UiNode, UiNodeRef, UiRect, UiSnapshot,
70    UiSnapshotOmissionReason, UiSnapshotRef, WAIT_FOR_ELEMENT_ACTION, WaitForElementArguments,
71    WaitForElementCondition, WaitForElementResult, is_semantic_action_name,
72};
73pub use wire_integer::{MAX_SAFE_INTEGER, json_integer_as_i32, json_integer_as_u32};
74
75#[cfg(test)]
76mod tests {
77    use serde_json::json;
78    use uuid::Uuid;
79
80    use super::{
81        ActionCall, EventId, EventSequence, HelloParams, HelloResult, JsonRpcVersion, RpcId,
82        RpcParams, RpcRequest, RpcResponse, SessionId, TestEvent, TestEventPayload,
83    };
84
85    #[test]
86    fn action_call_uses_camel_case_json() {
87        let call = ActionCall {
88            id: Uuid::nil(),
89            name: "tap".to_owned(),
90            arguments: json!({ "x": 10, "y": 20 }),
91        };
92
93        let value = serde_json::to_value(call).expect("serialize action call");
94        assert_eq!(value["id"], Uuid::nil().to_string());
95        assert_eq!(value["arguments"]["x"], 10);
96    }
97
98    #[test]
99    fn rpc_request_round_trips() {
100        let request = RpcRequest {
101            jsonrpc: JsonRpcVersion::V2,
102            id: RpcId::Number(42),
103            method: "device.observe".to_owned(),
104            timeout_ms: None,
105            params: Some(RpcParams::Object(Default::default())),
106        };
107
108        let serialized = serde_json::to_string(&request).expect("serialize request");
109        let restored: RpcRequest = serde_json::from_str(&serialized).expect("restore request");
110        assert_eq!(restored, request);
111    }
112
113    #[test]
114    fn event_type_is_explicit() {
115        let event = TestEvent {
116            event_id: EventId::from(Uuid::nil()),
117            session_id: SessionId::from(Uuid::nil()),
118            sequence: EventSequence::FIRST,
119            request_id: None,
120            device_id: None,
121            at_ms: 1,
122            payload: TestEventPayload::SessionStarted,
123        };
124
125        let value = serde_json::to_value(event).expect("serialize event");
126        assert_eq!(value["payload"]["type"], "sessionStarted");
127        assert_eq!(value["atMs"], 1);
128    }
129
130    #[test]
131    fn system_hello_golden_fixtures_round_trip() {
132        let request_value: serde_json::Value =
133            serde_json::from_str(include_str!("../fixtures/system-hello-v1.request.json"))
134                .expect("read hello request fixture");
135        let request: RpcRequest = serde_json::from_value(request_value.clone())
136            .expect("deserialize hello request fixture");
137        let hello_params: HelloParams =
138            serde_json::from_value(request.params.clone().expect("hello params").into_value())
139                .expect("deserialize typed hello params");
140        assert_eq!(
141            serde_json::to_value(hello_params).expect("serialize typed hello params"),
142            request_value["params"]
143        );
144        assert_eq!(
145            serde_json::to_value(request).expect("serialize hello request fixture"),
146            request_value
147        );
148
149        let response_value: serde_json::Value =
150            serde_json::from_str(include_str!("../fixtures/system-hello-v1.response.json"))
151                .expect("read hello response fixture");
152        let response: RpcResponse = serde_json::from_value(response_value.clone())
153            .expect("deserialize hello response fixture");
154        let hello_result: HelloResult =
155            serde_json::from_value(response.result().expect("hello result").clone())
156                .expect("deserialize typed hello result");
157        assert_eq!(
158            serde_json::to_value(hello_result).expect("serialize typed hello result"),
159            response_value["result"]
160        );
161        assert_eq!(
162            serde_json::to_value(response).expect("serialize hello response fixture"),
163            response_value
164        );
165    }
166}