…eceptjs#5677)
`mobile: setConnectivity` runs `adb shell svc <type> <state>` for every
field it is passed, so always sending `data` made setNetworkConnection()
fail with "Can't find service: phone" (adb exit 20) on system images
without telephony, such as tablet emulators. This regressed in 4.1.0,
when codeceptjs#5662 replaced the legacy setNetworkConnection command, which
tolerated the missing phone service.
Probe `mobile: deviceInfo` for a carrier and only send `data` when the
device actually has telephony. Dropping `data` unconditionally is not an
option: on a telephony-capable device it leaves cellular up, so going
offline silently does nothing and tests keep passing against a device
that is still online.
Only a positive probe result is cached. A freshly booted device may not
have registered a carrier yet, and since the first call typically comes
from a suite-setup hook seconds after boot, caching that negative would
strip `data` for the rest of the session on a real phone. Guarding with
`!this._hasTelephony` re-asks until a carrier appears, then stops.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes #5677
Problem
mobile: setConnectivity runs adb shell svc <type> <state> for every field it is passed, so always sending data makes setNetworkConnection() fail on system images without telephony (e.g. tablet emulators, ro.build.characteristics=tablet):
This is a regression in 4.1.0 — #5662 replaced the legacy setNetworkConnection command, which tolerated the missing phone service. It takes down whole suites when the call sits in a _beforeSuite hook.
Fix
Probe mobile: deviceInfo for a carrier and only send data when the device actually has telephony.
Dropping data unconditionally is not an option: on a telephony-capable device it leaves cellular up, so setNetworkConnection(1) keeps the device online and "go offline" silently does nothing.
# Android 13 emulator (has telephony), after mobile: setConnectivity {airplaneMode:true, wifi:false} -> Active default network: 106 (MOBILE still CONNECTED) {airplaneMode:true, wifi:false, data:false} -> Active default network: noneOnly a positive probe result is cached. The first setNetworkConnection of a run typically happens in a suite-setup hook, seconds after the emulator boots — a real phone whose modem has not registered a carrier yet would report an empty carrierName there, and caching that negative would strip data for the rest of the session. Guarding with !this._hasTelephony re-asks until a carrier appears, then stops probing.
The cost is one extra mobile: deviceInfo per call on a genuinely telephony-less device. That is a plain info read, so unlike the send-and-catch alternative it provokes no exception and adds no red ERROR webdriver: line to tablet runs.
carrierName is still reported while airplane mode is on, so the probe is not perturbed by the connectivity state being changed. grabNetworkConnection() is unaffected — mobile: getConnectivity works on these images.
Verified
Unit tests added for the telephony-less payload, the re-probe while no carrier has been seen, and probe-once-then-cache after a carrier appears. Full file passes (11 passing).
Verified as a patch against 4.1.0 on two live emulators — sdk_gtablet_x86_64 (Android 15, no telephony) and sdk_gphone64_x86_64 (Android 13, carrier T-Mobile):
No ERROR webdriver: line and no Can't find service: phone in any run. StudentAttendance is the test that originally caught the dropped-data failure, so its 54 passing scenarios confirm going offline still genuinely disconnects on a telephony-capable device.
🤖 Generated with Claude Code