mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
fix(ios-qa): allow physical iPad devices in tunnel bootstrap (#2743)
This commit is contained in:
1 parent
0d1bd5616c
commit
d6d43934ef
2 files changed
+63
-23
No files matched your search
@@ -62,7 +62,7 @@ export type BootstrapErrorReason =
|
||||
| 'rotate_failed'
|
||||
| 'resolve_failed';
|
||||
|
||||
function isIPhoneDevice(device: DeviceEntry): boolean {
|
||||
function isSupportedIOSDevice(device: DeviceEntry): boolean {
|
||||
const platform = device.platform.trim().toLowerCase();
|
||||
const deviceType = device.deviceType.trim().toLowerCase();
|
||||
const model = device.model.trim().toLowerCase();
|
||||
@@ -70,8 +70,13 @@ function isIPhoneDevice(device: DeviceEntry): boolean {
|
||||
// productType is present even on older CoreDevice versions. Prefer the
|
||||
// explicit platform/type fields when available, but retain productType as
|
||||
// a compatibility fallback. An explicit non-iOS platform always loses.
|
||||
if (platform && platform !== 'ios') return false;
|
||||
return deviceType === 'iphone' || model.startsWith('iphone');
|
||||
if (platform && platform !== 'ios' && platform !== 'ipados') return false;
|
||||
return (
|
||||
deviceType === 'iphone'
|
||||
|| deviceType === 'ipad'
|
||||
|| model.startsWith('iphone')
|
||||
|| model.startsWith('ipad')
|
||||
);
|
||||
}
|
||||
|
||||
function isAvailableDevice(device: Pick<DeviceEntry, 'state' | 'transport'>): boolean {
|
||||
@@ -87,7 +92,7 @@ function isAvailableDevice(device: Pick<DeviceEntry, 'state' | 'transport'>): bo
|
||||
}
|
||||
|
||||
function defaultDeviceRank(device: DeviceEntry): number {
|
||||
if (!device.paired || !isIPhoneDevice(device) || !isAvailableDevice(device)) return -1;
|
||||
if (!device.paired || !isSupportedIOSDevice(device) || !isAvailableDevice(device)) return -1;
|
||||
|
||||
const state = device.state.trim().toLowerCase();
|
||||
const transport = device.transport.trim().toLowerCase();
|
||||
@@ -161,40 +166,40 @@ export async function bootstrapTunnel(opts: BootstrapOptions): Promise<Bootstrap
|
||||
if (opts.udid) {
|
||||
return { ok: false, error: 'device_not_found', detail: opts.udid };
|
||||
}
|
||||
const pairedIPhone = devices.find((d) => d.paired && isIPhoneDevice(d));
|
||||
if (pairedIPhone) {
|
||||
const pairedIOS = devices.find((d) => d.paired && isSupportedIOSDevice(d));
|
||||
if (pairedIOS) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'device_not_found',
|
||||
detail: `paired iPhone ${pairedIPhone.name} (${pairedIPhone.identifier}) is ${pairedIPhone.state}; connect it over USB and unlock it`,
|
||||
detail: `paired device ${pairedIOS.name} (${pairedIOS.identifier}) is ${pairedIOS.state}; connect it over USB and unlock it`,
|
||||
};
|
||||
}
|
||||
const firstIPhone = devices.find(isIPhoneDevice);
|
||||
if (!firstIPhone) {
|
||||
const firstIOS = devices.find(isSupportedIOSDevice);
|
||||
if (!firstIOS) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'device_not_found',
|
||||
detail: 'no iPhone is connected; non-iOS devices are not eligible for iOS QA',
|
||||
detail: 'no iPhone or iPad is connected; non-iOS devices are not eligible for iOS QA',
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error: 'no_paired_device',
|
||||
detail: `device ${firstIPhone.name} (${firstIPhone.identifier}) is ${firstIPhone.state}; run \`xcrun devicectl manage pair --device ${firstIPhone.identifier}\` and tap Trust on the iPhone`,
|
||||
detail: `device ${firstIOS.name} (${firstIOS.identifier}) is ${firstIOS.state}; run \`xcrun devicectl manage pair --device ${firstIOS.identifier}\` and tap Trust on the device`,
|
||||
};
|
||||
}
|
||||
if (!isIPhoneDevice(target)) {
|
||||
if (!isSupportedIOSDevice(target)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'device_not_found',
|
||||
detail: `device ${target.name} (${target.identifier}) is ${target.platform || target.model}, not an iPhone`,
|
||||
detail: `device ${target.name} (${target.identifier}) is ${target.platform || target.model}, not an iPhone or iPad`,
|
||||
};
|
||||
}
|
||||
if (!target.paired) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'no_paired_device',
|
||||
detail: `device ${target.name} (${target.identifier}) is ${target.state}; run \`xcrun devicectl manage pair --device ${target.identifier}\` and tap Trust on the iPhone`,
|
||||
detail: `device ${target.name} (${target.identifier}) is ${target.state}; run \`xcrun devicectl manage pair --device ${target.identifier}\` and tap Trust on the device`,
|
||||
};
|
||||
}
|
||||
if (!isAvailableDevice(target)) {
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('bootstrapTunnel', () => {
|
||||
if (r.ok) expect(r.tunnel.udid).toBe('IPHONE');
|
||||
});
|
||||
|
||||
test('fails clearly when only non-iPhone platforms are connected', async () => {
|
||||
test('fails clearly when only non-iOS platforms are connected', async () => {
|
||||
const spawn = makeSpawn([
|
||||
{
|
||||
argsMatch: /devicectl list devices/,
|
||||
@@ -175,13 +175,13 @@ describe('bootstrapTunnel', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
identifier: 'IPAD',
|
||||
identifier: 'WATCH',
|
||||
connectionProperties: { tunnelState: 'connected', pairingState: 'paired' },
|
||||
deviceProperties: { name: 'iPad' },
|
||||
deviceProperties: { name: 'Apple Watch' },
|
||||
hardwareProperties: {
|
||||
productType: 'iPad16,6',
|
||||
platform: 'iOS',
|
||||
deviceType: 'iPad',
|
||||
productType: 'Watch7,5',
|
||||
platform: 'watchOS',
|
||||
deviceType: 'watch',
|
||||
},
|
||||
},
|
||||
] },
|
||||
@@ -193,11 +193,11 @@ describe('bootstrapTunnel', () => {
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {
|
||||
expect(r.error).toBe('device_not_found');
|
||||
expect(r.detail).toContain('no iPhone');
|
||||
expect(r.detail).toContain('no iPhone or iPad');
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects an explicitly targeted non-iPhone device', async () => {
|
||||
test('rejects an explicitly targeted non-iPhone/iPad device', async () => {
|
||||
const spawn = makeSpawn([
|
||||
{
|
||||
argsMatch: /devicectl list devices/,
|
||||
@@ -220,7 +220,42 @@ describe('bootstrapTunnel', () => {
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {
|
||||
expect(r.error).toBe('device_not_found');
|
||||
expect(r.detail).toContain('not an iPhone');
|
||||
expect(r.detail).toContain('not an iPhone or iPad');
|
||||
}
|
||||
});
|
||||
|
||||
test('accepts a physical iPad alongside iPhones', async () => {
|
||||
const spawn = makeSpawn([
|
||||
{
|
||||
argsMatch: /devicectl list devices/,
|
||||
jsonOutput: {
|
||||
result: { devices: [{
|
||||
identifier: 'IPAD-14-5',
|
||||
connectionProperties: { tunnelState: 'connected', pairingState: 'paired', transportType: 'wired' },
|
||||
deviceProperties: { name: 'Test iPad' },
|
||||
hardwareProperties: {
|
||||
productType: 'iPad14,5',
|
||||
platform: 'iOS',
|
||||
deviceType: 'iPad',
|
||||
},
|
||||
}] },
|
||||
},
|
||||
},
|
||||
{
|
||||
argsMatch: /devicectl device process launch --device IPAD-14-5/,
|
||||
exitCode: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
// Fast-fail at resolution stage so we verify device acceptance
|
||||
const r = await bootstrapTunnel({
|
||||
bundleId: 'com.test',
|
||||
spawnImpl: spawn,
|
||||
resolveImpl: () => Promise.resolve(null),
|
||||
});
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) {
|
||||
expect(r.error).toBe('resolve_failed');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user