MobDev.Differential (mob_dev v0.7.0)

Copy Markdown View Source

Drive Mob.Differential.compare/3 against two live device nodes.

Mob's product claim is one design, both platforms. mob ships the pure comparator; this module is the observer side of that: sample the semantic tree from each device, feed the pair through the comparator, report.

The comparator itself runs on one of the device nodes, via :rpc.call, not in this process. Two reasons:

  • A device with a recent mob already has Mob.Differential loaded. The host (this process) may not: mob_dev pins the mob dep at a Hex version that need not match what the app was built against.
  • Feeding the trees to a comparator on the device keeps the tree payloads from crossing the dist boundary twice.

The iOS node is chosen by convention: iOS shipped first in this project, and the comparator is symmetric (either device would give the same answer). If the iOS node's mob does not carry Mob.Differential yet, the call falls over to Android; if neither side does, that is reported as {:error, :differential_unavailable} rather than being hidden as a divergence. Note the semantic implication: while both devices have the comparator, the iOS device's mob version decides what "compare" means. If a comparator refinement lands on one platform before the other, the answer follows the iOS build, not the newer of the two.

What this does not do yet

  • No fixture management. Both apps are already running the screen you want to compare; orchestrating the same fixture across two devices is a separate change on top of this.
  • No sink. The result is returned to the caller; wiring divergences into a defect bus is MOB-159.

Usage

MobDev.Differential.run(:"my_app_ios@127.0.0.1", :"my_app_android_emulator_5554@127.0.0.1")
#=> :ok | {:divergence, %{path: [...], reason: :type, ios: ..., android: ...}}
#=> {:error, :not_ready | :differential_unavailable | {:tree_error | :comparator_error, node, term}}

Summary

Types

Node name reachable over Erlang distribution.

Reason a run could not produce a comparison result.

Types

node_name()

@type node_name() :: node()

Node name reachable over Erlang distribution.

result()

@type result() :: :ok | {:divergence, map()} | {:error, run_error()}

run_error()

@type run_error() ::
  :not_ready
  | :differential_unavailable
  | {:tree_error, node_name(), term()}
  | {:comparator_error, node_name(), term()}

Reason a run could not produce a comparison result.

Functions

run(ios_node, android_node, opts \\ [])

@spec run(node_name(), node_name(), keyword()) :: result()

Sample Mob.Test.view_tree/1 on both nodes and compare.

Options:

  • :frame_tolerance_dp - forwarded to Mob.Differential.compare/3.
  • :rpc_timeout - per-RPC timeout in ms. Default 15000. A run performs up to three sequential RPCs (iOS sample, Android sample, one comparator dispatch), so the worst-case wall clock is roughly 3 * rpc_timeout.
  • :rpc - module implementing :rpc.call/5. For tests. Real callers should not pass this.

Unknown keys raise ArgumentError rather than being silently ignored: a run that thinks it tightened frame_tolerance_dp but actually used the default is worse than a loud failure.