Skip to content

Commit e336a75

Browse files
Jaromir Obrclaude
andcommitted
fix(appium): skip data connectivity on devices without telephony (#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 #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>
1 parent 8b91815 commit e336a75

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

lib/helper/Appium.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -978,11 +978,23 @@ class Appium extends Webdriver {
978978
*/
979979
async setNetworkConnection(value) {
980980
onlyForApps.call(this, supportedPlatform.android)
981-
return this.browser.execute('mobile: setConnectivity', {
981+
const connectivity = {
982982
airplaneMode: !!(value & 1),
983983
wifi: !!(value & 2),
984984
data: !!(value & 4),
985-
})
985+
}
986+
// `mobile: setConnectivity` runs `adb shell svc data <state>` for every field it receives,
987+
// which fails with "Can't find service: phone" on images without telephony (e.g. tablets).
988+
// Only a positive result is cached: a freshly booted device may not have registered a carrier
989+
// yet, and caching that would strip `data` for the whole session.
990+
if (!this._hasTelephony) {
991+
const deviceInfo = await this.browser.execute('mobile: deviceInfo')
992+
this._hasTelephony = !!deviceInfo?.carrierName
993+
}
994+
// Keep `data` on telephony-capable devices, otherwise the device stays online over cellular
995+
// and going offline silently does nothing.
996+
if (!this._hasTelephony) delete connectivity.data
997+
return this.browser.execute('mobile: setConnectivity', connectivity)
986998
}
987999

9881000
/**

test/unit/helper/Appium_networkConnection_test.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { expect } from 'chai'
22
import sinon from 'sinon'
33
import Appium from '../../../lib/helper/Appium.js'
44

5-
function createApp() {
5+
function createApp(deviceInfo = { carrierName: 'T-Mobile' }) {
66
const app = new Appium({
77
platform: 'Android',
88
desiredCapabilities: {
99
platformName: 'Android',
1010
},
1111
})
1212
app.browser = { execute: sinon.stub() }
13+
app.browser.execute.withArgs('mobile: deviceInfo').resolves(deviceInfo)
1314
return app
1415
}
1516

@@ -44,6 +45,29 @@ describe('Appium #setNetworkConnection, #grabNetworkConnection', () => {
4445
expect(app.browser.execute.calledWith('mobile: setConnectivity', { airplaneMode: false, wifi: false, data: false })).to.be.true
4546
})
4647

48+
it('should omit data on devices without telephony', async () => {
49+
const app = createApp({ carrierName: '' })
50+
await app.setNetworkConnection(1)
51+
expect(app.browser.execute.calledWith('mobile: setConnectivity', { airplaneMode: true, wifi: false })).to.be.true
52+
})
53+
54+
it('should probe telephony again while no carrier was seen yet', async () => {
55+
const app = createApp({ carrierName: '' })
56+
await app.setNetworkConnection(1)
57+
app.browser.execute.withArgs('mobile: deviceInfo').resolves({ carrierName: 'T-Mobile' })
58+
await app.setNetworkConnection(2)
59+
expect(app.browser.execute.withArgs('mobile: deviceInfo').callCount).to.equal(2)
60+
expect(app.browser.execute.calledWith('mobile: setConnectivity', { airplaneMode: false, wifi: true, data: false })).to.be.true
61+
})
62+
63+
it('should probe telephony only once after a carrier was seen', async () => {
64+
const app = createApp()
65+
await app.setNetworkConnection(1)
66+
await app.setNetworkConnection(6)
67+
expect(app.browser.execute.withArgs('mobile: deviceInfo').callCount).to.equal(1)
68+
expect(app.browser.execute.calledWith('mobile: setConnectivity', { airplaneMode: false, wifi: true, data: true })).to.be.true
69+
})
70+
4771
it('should grab network connection using mobile: getConnectivity and map to legacy bitmask shape', async () => {
4872
const app = createApp()
4973
app.browser.execute.resolves({ airplaneMode: false, wifi: false, data: true })

0 commit comments

Comments
 (0)