-
-
Notifications
You must be signed in to change notification settings - Fork 241
Fix empty accessibility hierarchy on iOS 26+ simulators #312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dpearson2699
wants to merge
1
commit into
getsentry:main
Choose a base branch
from
dpearson2699:fix/290
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import { describe, it, expect } from 'vitest'; | ||
| import { | ||
| createMockExecutor, | ||
| createCommandMatchingMockExecutor, | ||
| createMockCommandResponse, | ||
| } from '../../test-utils/mock-executors.ts'; | ||
| import type { CommandExecutor } from '../execution/index.ts'; | ||
| import { ensureSimulatorAccessibility } from '../simulator-accessibility.ts'; | ||
|
|
||
| const SIM_UUID = '12345678-1234-4234-8234-123456789012'; | ||
|
|
||
| describe('ensureSimulatorAccessibility', () => { | ||
| it('should always write both accessibility flags', async () => { | ||
| const executorCalls: string[][] = []; | ||
| const mockExecutor = createCommandMatchingMockExecutor({ | ||
| 'defaults write': { success: true, output: '' }, | ||
| }); | ||
|
|
||
| const trackingExecutor: CommandExecutor = async (...args) => { | ||
| executorCalls.push(args[0] as string[]); | ||
| return mockExecutor(...args); | ||
| }; | ||
|
|
||
| await ensureSimulatorAccessibility(SIM_UUID, trackingExecutor); | ||
|
|
||
| expect(executorCalls).toHaveLength(2); | ||
| expect(executorCalls[0].join(' ')).toContain('AccessibilityEnabled'); | ||
| expect(executorCalls[0].join(' ')).toContain('defaults write'); | ||
| expect(executorCalls[1].join(' ')).toContain('ApplicationAccessibilityEnabled'); | ||
| expect(executorCalls[1].join(' ')).toContain('defaults write'); | ||
| }); | ||
|
|
||
| it('should not throw when executor throws', async () => { | ||
| const mockExecutor = createMockExecutor(new Error('spawn failed')); | ||
|
|
||
| // Should not throw | ||
| await ensureSimulatorAccessibility(SIM_UUID, mockExecutor); | ||
| }); | ||
|
|
||
| it('should attempt second write even when first executor call throws', async () => { | ||
| const executorCalls: string[][] = []; | ||
| const callCount = { n: 0 }; | ||
| const mockExecutor: CommandExecutor = async (command) => { | ||
| executorCalls.push(command as string[]); | ||
| callCount.n++; | ||
| if (callCount.n === 1) { | ||
| throw new Error('spawn failed'); | ||
| } | ||
| return createMockCommandResponse({ success: true, output: '' }); | ||
| }; | ||
|
|
||
| await ensureSimulatorAccessibility(SIM_UUID, mockExecutor); | ||
|
|
||
| // Second write should still be attempted even when first throws | ||
| expect(executorCalls).toHaveLength(2); | ||
| expect(executorCalls[1].join(' ')).toContain('ApplicationAccessibilityEnabled'); | ||
| }); | ||
|
|
||
| it('should attempt both writes even when first write fails', async () => { | ||
| const executorCalls: string[][] = []; | ||
| const callCount = { n: 0 }; | ||
| const mockExecutor: CommandExecutor = async (command) => { | ||
| executorCalls.push(command as string[]); | ||
| callCount.n++; | ||
| if (callCount.n === 1) { | ||
| return createMockCommandResponse({ success: false, error: 'write failed' }); | ||
| } | ||
| return createMockCommandResponse({ success: true, output: '' }); | ||
| }; | ||
|
|
||
| await ensureSimulatorAccessibility(SIM_UUID, mockExecutor); | ||
|
|
||
| // Both writes should be attempted even when first fails | ||
| expect(executorCalls).toHaveLength(2); | ||
| }); | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| it('should not throw when first write fails', async () => { | ||
| const mockExecutor = createCommandMatchingMockExecutor({ | ||
| 'AccessibilityEnabled -bool': { success: false, error: 'write failed' }, | ||
| }); | ||
|
|
||
| // Should not throw | ||
| await ensureSimulatorAccessibility(SIM_UUID, mockExecutor); | ||
| }); | ||
|
|
||
| it('should pass correct simctl spawn commands', async () => { | ||
| const executorCalls: string[][] = []; | ||
| const mockExecutor: CommandExecutor = async (command) => { | ||
| executorCalls.push(command as string[]); | ||
| return createMockCommandResponse({ success: true, output: '' }); | ||
| }; | ||
|
|
||
| await ensureSimulatorAccessibility(SIM_UUID, mockExecutor); | ||
|
|
||
| expect(executorCalls[0]).toEqual([ | ||
| 'xcrun', | ||
| 'simctl', | ||
| 'spawn', | ||
| SIM_UUID, | ||
| 'defaults', | ||
| 'write', | ||
| 'com.apple.Accessibility', | ||
| 'AccessibilityEnabled', | ||
| '-bool', | ||
| 'true', | ||
| ]); | ||
| expect(executorCalls[1]).toEqual([ | ||
| 'xcrun', | ||
| 'simctl', | ||
| 'spawn', | ||
| SIM_UUID, | ||
| 'defaults', | ||
| 'write', | ||
| 'com.apple.Accessibility', | ||
| 'ApplicationAccessibilityEnabled', | ||
| '-bool', | ||
| 'true', | ||
| ]); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import type { CommandExecutor } from './execution/index.ts'; | ||
| import { log } from './logging/index.ts'; | ||
|
|
||
| const LOG_PREFIX = '[Simulator]'; | ||
|
|
||
| /** | ||
| * Ensure accessibility defaults are enabled on a simulator. | ||
| * On iOS 26+ fresh simulators, AccessibilityEnabled and ApplicationAccessibilityEnabled | ||
| * default to 0, which prevents accessibility hierarchy queries from returning any elements. | ||
| * | ||
| * Both flags are written unconditionally on every call — defaults write is idempotent | ||
| * and avoids a partial-state problem where only checking one flag could skip the second. | ||
| * Failures are logged but never propagated — accessibility setup should not block boot. | ||
dpearson2699 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| */ | ||
| export async function ensureSimulatorAccessibility( | ||
| simulatorId: string, | ||
| executor: CommandExecutor, | ||
| ): Promise<void> { | ||
| let a11yOk = false; | ||
| let appA11yOk = false; | ||
|
|
||
| try { | ||
| const writeA11y = await executor( | ||
| [ | ||
| 'xcrun', | ||
| 'simctl', | ||
| 'spawn', | ||
| simulatorId, | ||
| 'defaults', | ||
| 'write', | ||
| 'com.apple.Accessibility', | ||
| 'AccessibilityEnabled', | ||
| '-bool', | ||
| 'true', | ||
| ], | ||
| `${LOG_PREFIX}: enable AccessibilityEnabled`, | ||
| ); | ||
| a11yOk = writeA11y.success; | ||
| if (!a11yOk) { | ||
| log('warn', `${LOG_PREFIX}: Failed to enable AccessibilityEnabled: ${writeA11y.error}`); | ||
| } | ||
| } catch (error) { | ||
| log( | ||
| 'warn', | ||
| `${LOG_PREFIX}: Failed to enable AccessibilityEnabled: ${error instanceof Error ? error.message : String(error)}`, | ||
| ); | ||
| } | ||
|
|
||
| try { | ||
| const writeAppA11y = await executor( | ||
| [ | ||
| 'xcrun', | ||
| 'simctl', | ||
| 'spawn', | ||
| simulatorId, | ||
| 'defaults', | ||
| 'write', | ||
| 'com.apple.Accessibility', | ||
| 'ApplicationAccessibilityEnabled', | ||
| '-bool', | ||
| 'true', | ||
| ], | ||
| `${LOG_PREFIX}: enable ApplicationAccessibilityEnabled`, | ||
| ); | ||
| appA11yOk = writeAppA11y.success; | ||
| if (!appA11yOk) { | ||
| log( | ||
| 'warn', | ||
| `${LOG_PREFIX}: Failed to enable ApplicationAccessibilityEnabled: ${writeAppA11y.error}`, | ||
| ); | ||
| } | ||
| } catch (error) { | ||
| log( | ||
| 'warn', | ||
| `${LOG_PREFIX}: Failed to enable ApplicationAccessibilityEnabled: ${error instanceof Error ? error.message : String(error)}`, | ||
| ); | ||
| } | ||
dpearson2699 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (a11yOk && appA11yOk) { | ||
| log('info', `${LOG_PREFIX}: Accessibility defaults enabled for simulator ${simulatorId}`); | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.