skip to content

What makes a swipe or drag case reproducible when direction and speed change the outcome?

level: middleimportance: should knowfreq 46%

answer

  1. A swipe carries more than a direction
  2. Distance and speed change the outcome
  3. Pixels recorded on one screen do not travel
  4. Endpoints as fractions of the element
  5. State the contact duration in the case

basics

~20 s

Declare the gesture in terms the case owns: endpoints as fractions of the target element, a stated direction, and an explicit contact duration. Recorded pixel coordinates and default speeds differ per device, so one script runs a different gesture.

solid answer

~50 s

A swipe is not one thing. It carries a **direction**, a **distance** and a **duration** - and therefore a speed - and products branch on all of them: a slow drag past a commit threshold reveals a row's actions, while the same path flicked quickly carries momentum and dismisses the row. A case that says only "swipe left" has specified one of those and left the rest to whatever default the harness carries, and that default varies with device, density and tooling version. Make every parameter a value the case owns: **endpoints expressed as fractions of the target element's bounds** rather than absolute pixels, the direction stated in the case name, and an **explicit duration** so the velocity is yours. Then assert the branch you meant to trigger, and write the sibling case for the branch you did not.

code

pseudocode · 16 lines
pseudocode
SETTLE_MS = 400   # slow enough to be a drag, not a fling
FLING_MS  = 90

def drag_across(element, from_fraction, to_fraction, duration_ms):
    y       = element.top  + element.height * 0.5
    x_start = element.left + element.width  * from_fraction
    x_end   = element.left + element.width  * to_fraction
    drag(from = (x_start, y), to = (x_end, y), duration = duration_ms)

case "dragging past the commit threshold reveals the delete action":
    drag_across(row, from_fraction = 0.9, to_fraction = 0.1, duration_ms = SETTLE_MS)
    assert row.delete_action.is_visible()

case "dragging short of the threshold springs the row back":
    drag_across(row, from_fraction = 0.9, to_fraction = 0.75, duration_ms = SETTLE_MS)
    assert not row.delete_action.is_visible()

go deeper

for a junior

Be ready to say that a swipe carries a direction, a distance and a duration, and that the product can behave differently for each — a slow drag and a fast flick along the same path are not the same input.

for a middle

Explain why absolute pixel endpoints do not survive a change of screen size or density, and how endpoints stated as fractions of the target element's bounds keep one declaration valid everywhere.

for a senior

Show how you pin velocity deliberately, with one duration for the case that must settle and a much shorter one for the case that must fling, and how you cover both sides of a commit threshold instead of one value in the middle.

for a principal

Own where gesture parameters live. Thresholds and durations scattered through hundreds of cases become forty edits when one of them moves; a single declared vocabulary makes that a one-line change and makes intent readable in review.

## A swipe carries four things, not one A moving contact is described by where it starts, where it ends, how long it stays down, and — derived from those — how fast it travelled. Products branch on every one of them. - **Direction** picks between opposite behaviours. A leftward drag across a list row commonly reveals a destructive action; a rightward drag across the same row marks the item complete. - **Distance** decides whether the interaction commits or springs back. Row actions typically stick only past a commit threshold expressed as a fraction of the row's width; release short of it and the row animates home. - **Duration**, and therefore velocity, separates a *drag* from a *fling*. The same path traced slowly settles where the contact lifted; traced quickly it carries momentum, overshoots, and may dismiss the element entirely. - **Origin** changes meaning even when the other three are identical. A downward drag starting at the top of a scrollable list triggers a refresh; the same drag starting in the middle is ordinary scrolling. A drag starting at a screen edge may be claimed by the system before the application ever sees it. A case that says "swipe left on the row" has specified one of those four. The other three are filled in by whatever default the harness carries, and nothing in the source tells a reader which gesture actually ran. ## Why recorded coordinates do not travel The tempting shortcut is to capture the gesture once on the machine in front of you and replay the numbers. | How the case expresses the gesture | Survives a change of device? | What goes wrong | |---|---|---| | Absolute pixel start and end points | No | a different size or density puts an endpoint outside the element | | A fraction of the whole screen | Partly | the element's position within the screen moves with the layout | | A fraction of the target element's bounds | Yes | needs the bounds at run time, but the path stays inside the element | | A replay of one captured contact trace | No | freezes both the geometry and the timing of a single machine | The failure this produces is the confusing kind: the case is green on the machine that recorded it and red — or, worse, green for the wrong reason — everywhere else. Absolute endpoints encode one screen size, one density and one layout. On a narrower screen a fixed travel no longer clears a threshold that is a fraction of the row; on a wider one the same travel overshoots into a neighbouring behaviour. ## Velocity is a product parameter, not noise Teams treat duration as noise because it does not appear in the case's intent — "swipe the row away" says nothing about speed. The product's recogniser reads it anyway. A slow drag past the commit threshold and a fast flick along a shorter path can produce the same outcome, while a fast flick along the full path can produce a different one, because momentum carries the element past a second boundary. So choose the duration from the behaviour under test and name it: - a **settle** duration, long enough that the contact is unambiguously a drag, for cases about thresholds and reveal states; - a **fling** duration, short enough to carry momentum, for cases about dismissal and overscroll; - never the harness default, which is neither, and which changes when the tooling is upgraded. ## Making the gesture a value the case owns 1. Express endpoints as **fractions of the target element's bounds**, resolved at run time. "From ninety per cent of the row's width to ten per cent" means the same thing on every screen. 2. Pass the **duration explicitly** on every gesture, from a named constant rather than a literal buried in one case. 3. Put the direction and the intent in the **case name**, so a red result reads "dragging past the commit threshold reveals the delete action" rather than "swipe test 3". 4. Keep the gesture helpers in **one place** the whole suite calls. When a threshold moves, that is one edit rather than forty. 5. Cover **both sides of every threshold**: one case just past it that commits, one case short of it that springs back. A single mid-range value drifts to whichever side a given device lands on, and drifts silently. 6. Never tune the distance until the case turns green. A number chosen that way encodes the machine it was tuned on and nothing else. ## Assert the branch, not the motion The last mistake is asserting that the gesture happened rather than that the intended branch ran. "The row moved" is true of the commit and of the spring-back alike. Assert the outcome the direction and speed were chosen to produce — the destructive action is now offered, the item has left the list, the list refreshed — and write the sibling case for the branch you did not take. A pair of cases that differ in exactly one declared parameter and assert opposite outcomes is the clearest evidence that a case controls its gesture rather than inheriting it.

  • The same drag commits on a large handheld and springs back on a small one. What is the likely cause?
    The travel distance is expressed in absolute units, so it clears the commit threshold on a wide screen and falls short on a narrow one. Thresholds are normally a fraction of the element's width rather than a fixed length. Re-express the endpoints as fractions of the target's bounds and one declaration behaves identically on both.
  • How do you write a case for a fling when the resting position is not deterministic?
    Do not assert the resting position. Assert the property the fling was meant to establish: the list travelled past a known item, the row left the list, the header collapsed. Momentum depends on device physics and on system motion settings, so pin the outcome the product promises rather than the pixel it stopped at.
  • A drag that begins at the very edge of the screen behaves inconsistently. Why?
    Edges are often reserved by the system for its own gestures, such as navigation or a control panel, and it may claim the contact before the application sees it. Start the drag inside the element's own bounds rather than at the screen boundary, and if the product genuinely binds an edge gesture, cover it in a case that says so explicitly.

saying these in an interview costs you the question

  • Records pixel coordinates once and replays them everywhere
  • Says swipe left and lets the harness choose the speed
  • Treats a fast flick and a slow drag as one gesture
  • Tunes the travel distance until the case goes green
  • Asserts the element moved rather than which branch ran