skip to content

How do you test an app on a shared network that answers requests with its own sign-in page?

level: seniorimportance: nice to knowfreq 22%

answer

  1. Not offline, and not the real service
  2. Something answered, but not your service
  3. The status looks fine, the body is a page
  4. Validate shape, never just the status
  5. Assert nothing intercepted was cached

basics

~20 s

Stand a programmable element in the request path that answers every call with a sign-in page instead of the expected payload. Assert the app rejects the impostor response, points the user at the network, and caches nothing.

solid answer

~50 s

A network that requires sign-in before it forwards traffic intercepts the app's calls and replies with **its own page**, often under a status the client reads as success. A client that checks only the status treats that page as data: it fails deep inside parsing, renders an empty screen, or writes the impostor response into its local cache. Stage it with a programmable element in the request path that returns a redirect to a sign-in page, or a success status whose body is that page, for every call. Then assert three things: the app **detects the response is not its payload** by shape rather than by status; it shows a message pointing at the network rather than an internal error; and nothing intercepted is persisted. On an encrypted connection with a verified server identity the same interception surfaces as a hard connection failure, so write both legs.

code

pseudocode · 14 lines
pseudocode
case "app on a network that requires sign-in first":
    path_element.answer_all_with(
        status = success,
        body   = sign_in_page_body,
        kind   = "page, not the expected payload")

    open(home_screen)
    expect visible(message_about_network_sign_in)
    expect not visible(internal_parse_error)
    expect local_cache.entry_for(home_feed) == unchanged

    path_element.stop_intercepting()
    retry(home_screen)
    expect visible(real_feed)          # recovers with no restart

go deeper

for a junior

Be ready to say what makes this different from having no connection at all: something does answer, it just is not your service. Recall that a success status alone never proves the body is your payload.

for a middle

Explain how to stage it - a programmable element in the request path answering every call with a sign-in page - and why shape validation rather than status checking is the fix.

for a senior

Show the judgment: cover both the plain and the encrypted leg, assert that nothing intercepted is cached, check recovery after the interception lifts, and decide honestly whether the product's users meet these networks often enough to keep the case.

for a principal

Own the general rule this scenario teaches - a client trusts a response for its shape, never for the fact that something replied - and decide where that validation belongs so every call inherits it rather than each screen re-implementing it.

Some shared networks - the ones in hotels, cafes, airports and conference halls - hold traffic until a person accepts terms or signs in. Until that happens, the network does not drop the app's requests. It **answers them itself**, with a page of its own. That distinction is the whole topic: the client is not offline, and it is not talking to the service. It is talking to something that looks like a server and is not. ## Why this breaks clients that handle "no network" perfectly Most apps have a well-tested disconnected path and a well-tested happy path. This scenario is neither, and it lands in the gap: - The call **succeeds** at the transport level, so any code branching on "did the request fail" takes the success branch. - The status is frequently one the client reads as success, or a redirect the client's own machinery follows silently. - The body is a page, not the expected payload, so parsing fails - typically deep inside, with an error message about an unexpected character rather than about the network. - The user sees an internal-looking error, a blank screen, or a spinner that never resolves, none of which suggests the actual remedy: open a browser and sign in to the network. The nastiest variant is a client that **caches** what it received. Now the impostor page is the stored answer for that request, and the app stays broken after the user signs in to the network and everything else works. ## Two legs, two failure shapes The interception behaves differently depending on whether the connection is encrypted with a verified server identity: | Leg | What the client receives | What correct behaviour looks like | | --- | --- | --- | | Plain, unverified connection | a success status carrying a sign-in page, or a redirect to one | detect that the body is not the expected payload; show a network sign-in prompt; cache nothing | | Encrypted, identity verified | a failed handshake or a broken connection, because the interceptor cannot present the expected identity | fail fast and clearly, and still name the network as the likely cause rather than blaming the service | Both legs deserve a case, and the second is the reason the first is often missed: teams whose traffic is encrypted assume they are immune. They are not - they get a different symptom, usually an unhelpful one, and a client that retries a failing handshake in a tight loop will drain the battery while showing nothing useful. ## Staging it You do not need a real captive network. You need something in the request path that behaves like one: 1. Put a **programmable element in the path** and configure it to answer every call with a redirect to a sign-in page, or with a success status whose body is that page. 2. Run the app's normal flows against it - first load, sign-in, a read screen, a write action. 3. For the encrypted leg, have the element present an identity the client will not accept, and assert the client's behaviour on a failed connection rather than on a bad body. 4. Then **lift the interception mid-run** and assert the app recovers on the next attempt without a restart and without serving anything it stored during the interception. Step four is the one people skip, and it is where the caching defect shows up. ## What to assert - The app **validates response shape**, not just the status - a payload it cannot recognise is rejected as not-ours. - The user-facing message points at the network needing sign-in, not at an internal parsing failure. - Nothing from the intercepted response reaches the local cache, the stored profile, or any screen that renders it as content. - No tight loop: repeated attempts are spaced, and the app stops hammering a path that keeps answering with the same page. - After the interception lifts, the next attempt succeeds without a restart. ## Is it worth a case at all? Honestly, for many products this is a differentiator rather than a gate. It earns a case when the app is used in public spaces by design - travel, events, retail, field work - or when the product's support load already shows it: tickets that read "the app is broken on hotel networks" are this defect nearly every time. For a product used mainly on trusted connections, one case on the plain leg plus a shape-validation rule applied everywhere is usually the right amount of effort. The general lesson generalises further than the scenario does: **a client should decide a response is valid by its shape, never by the fact that something answered.**

  • How should the app tell this apart from the service genuinely returning a page by mistake?
    It mostly should not try to. The safe rule is the same in both cases: a response whose shape is not the expected payload is rejected and nothing is stored. Distinguishing them is a nicety for the message wording - a heuristic such as a redirect to an unrelated address suggests the network, while an unexpected body from the expected address suggests the service - and it must never gate the rejection itself.
  • Why does encrypting all traffic not remove the need for this case?
    It changes the symptom rather than removing it. An interceptor that cannot present the expected server identity produces a failed connection instead of a plausible page, so the app never renders garbage - but it also has to fail fast, avoid retrying in a tight loop, and give the user a message that points at the network. Without a case, that path is usually an unhelpful generic error.

It is like a receptionist who intercepts every phone call and reads out the visitor rules instead of putting you through. The line works perfectly; the answer is simply not from who you called.

saying these in an interview costs you the question

  • Branches on whether the call failed, never on shape
  • Trusts a success status as proof of a real payload
  • Caches the intercepted page as the stored answer
  • Retries a failing connection in a tight loop
  • Shows a parsing error instead of a network prompt
  • Assumes encrypted traffic makes the scenario impossible