Skip to content

test(integration): gated ko-KR push-permission live suite (#660)#692

Merged
shaun0927 merged 3 commits into
developfrom
feat/660-d-ko-kr-test-scaffold
Apr 29, 2026
Merged

test(integration): gated ko-KR push-permission live suite (#660)#692
shaun0927 merged 3 commits into
developfrom
feat/660-d-ko-kr-test-scaffold

Conversation

@shaun0927
Copy link
Copy Markdown
Owner

Summary

  • Issue feat(ax-bridge): enumerate UNUserNotificationCenter permission sheet (#651 follow-up) #660 plan item: "Add integration test gated by a ko-KR simulator availability check in CI". Encodes the three Exit criteria from the issue body as Jest assertions.
  • Two tests in tests/integration/issue-660-ko-kr-push-permission.live.test.ts:
  • Gated by explicit opt-in: OPENSAFARI_KOKR_PUSH_DIALOG=1 + OSF_DEVICE_ID + OSF_PERMISSION_BUNDLE. Default behavior (no opt-in) is a single "suite skipped" assertion that prints a structured warning. CI does not set the opt-in env var so this PR does not change CI behavior.

Why opt-in instead of auto-detect

The cold-launch step requires xcrun simctl privacy <udid> reset notifications <bundle>, which fails with NSPOSIXErrorDomain code 1 ("Operation not permitted") on hosts whose shell does not have Full Disk Access for the simulator's TCC sandbox — the same wall the previous autopilot session and this one both hit. Auto-running the suite on a non-FDA host would surface the simctl error as a hard test failure even though the failure is environmental, not regression-driven.

The opt-in env var doubles as a contract: the operator who flips it accepts responsibility for ensuring the host environment is FDA-granted (recipe in docs/recipes/transient-simctl-errors.md).

Dependency on PR #691

Test B asserts the presence of walker_app_windows_enumerated, walker_top_candidates, and walker_overlay_roles_seen events. Those events ship in #691. Opt-in runs against a develop branch that includes #691 pass cleanly. Opt-in runs against an earlier develop will hit a partial-fail on Test B's event-name assertions; default-skip runs are unaffected because the assertions only execute inside the opt-in branch.

Test plan

  • Default-skip run (no opt-in env var): npx jest tests/integration/issue-660-ko-kr-push-permission.live.test.ts --runInBand passes 1 test with the structured skip warning
  • ESLint clean
  • Opt-in run on an FDA-granted ko-KR host: deferred until a focused session with the FDA grant runs. The suite is the verification surface, not a precondition.

🤖 Generated with Claude Code

Issue #660 plan item: "Add integration test gated by a ko-KR simulator
availability check in CI". Encodes the three Exit criteria from the
issue body as Jest assertions:

  1. app_handle_alert { action: 'dismiss' } returns dismissed: true,
     strategy: 'ax-scan'
  2. visibleButtons contains the localized labels (허용, 허용 안 함)
  3. ax-bridge-native dump --debug exits 0 and emits the walker debug
     events added in #691

The suite is gated behind explicit opt-in (OPENSAFARI_KOKR_PUSH_DIALOG=1
+ OSF_DEVICE_ID + OSF_PERMISSION_BUNDLE) because the cold-launch step
requires the host shell to have Full Disk Access for the simulator's
TCC sandbox so `simctl privacy reset notifications` succeeds. Without
the FDA grant, the suite skips with a structured warning instead of
failing CI on a host environment issue. See
docs/recipes/transient-simctl-errors.md for the FDA setup the suite
expects.

Default behavior (no opt-in) is a single-test "suite skipped"
assertion, so the file integrates cleanly into the existing
tests/integration set without needing a CI flag flip. When a focused
session has the FDA grant, opting in turns the same file into a
binary verification of the issue's Exit criteria.

The walker_* event assertions in the second test depend on the
diagnostic events landed in #691; opt-in runs against a develop branch
that includes #691 will pass cleanly, runs against an earlier develop
will hit a partial-fail on those events but only when opt-in is set.

Verified locally:
- Default-skip run: 1 test passes, prints opt-in skip warning
- ESLint clean

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new live integration test suite for issue #660, focusing on the Korean localization of the iOS notification permission sheet. The suite includes logic for resetting notification permissions, launching the target application, and verifying the presence and dismissibility of localized buttons. The review feedback correctly identifies that several critical failure points—such as a failed environment reset, an empty tool response, or the absence of the permission sheet—currently use console.warn and return early. This leads to false positives where the test passes without actually performing the verification. The reviewer recommends throwing explicit errors in these scenarios to ensure test reliability when the suite is opted into.

Comment on lines +201 to +204
if (!reset.ok) {
console.warn(`[issue-660] ${reset.reason}`);
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Once the test has opted in via OPENSAFARI_KOKR_PUSH_DIALOG=1, environmental failures like a simctl reset error should result in a test failure rather than a silent skip via console.warn. This ensures that the operator is alerted if the environment they've committed to providing is not actually ready.

    if (!reset.ok) {
      throw new Error(`[issue-660] ${reset.reason}`);
    }

Comment on lines +229 to +232
if (!lastBody) {
console.warn('[issue-660] tool handler returned no body — skipping');
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the reset check, if the tool handler fails to return a body, the test should fail to indicate that the expected interaction did not occur.

    if (!lastBody) {
      throw new Error('[issue-660] tool handler returned no body');
    }

Comment on lines +236 to +246
!visibleButtons.includes(ACCEPT_LABEL) &&
!visibleButtons.includes(DISMISS_LABEL)
) {
console.warn(
`[issue-660] permission sheet did not appear within ${SHEET_WAIT_MS} ms; ` +
`visibleButtons=${JSON.stringify(visibleButtons)}. ` +
'Skipping — likely a build flag or app behavior change.',
);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the permission sheet does not appear within the timeout, the test has failed to verify the fix for issue #660. Using console.warn and returning allows the test to pass incorrectly. Throwing an error here ensures the test correctly reports a failure when the localized buttons are not found.

    if (
      !visibleButtons.includes(ACCEPT_LABEL) &&
      !visibleButtons.includes(DISMISS_LABEL)
    ) {
      throw new Error(
        `[issue-660] permission sheet did not appear within ${SHEET_WAIT_MS} ms; ` +
          `visibleButtons=${JSON.stringify(visibleButtons)}.`
      );
    }

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 319edbc001

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +252 to +253
expect(visibleButtons).toEqual(
expect.arrayContaining([ACCEPT_LABEL, DISMISS_LABEL]),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize button labels before asserting ko-KR text

visibleButtons from app_handle_alert is a diagnostic field that may include whitespace annotations (e.g. "허용 안 함 (norm: 허용 안 함)") when SpringBoard uses NBSP or other non-ASCII whitespace, so asserting exact membership of raw "허용 안 함" will fail even when the correct button is present. In the iOS 26.4 ko-KR scenario this makes the live test report a regression for a passing behavior; compare against normalized/stripped labels before this assertion.

Useful? React with 👍 / 👎.

Address gemini-code-assist (3) and chatgpt-codex-connector (1) review
items on PR #692:

- gemini@:204, :232, :246: once OPENSAFARI_KOKR_PUSH_DIALOG=1 has been
  flipped, environmental failures (TCC reset error, empty tool body,
  missing permission sheet) are setup errors that the operator alone
  can resolve and must surface as test failures rather than silent
  console.warn skips. Skip-with-warn behavior is preserved for the
  default no-opt-in path.

- codex@:253: collectVisibleButtonLabels in app_handle_alert annotates
  non-ASCII whitespace as "<original> (norm: <normalized>)" (slice 2
  of #642), and SpringBoard ko-KR permission sheets use NBSP between
  the syllables of 허용 안 함. Strict equality against the raw constant
  would falsely fail when the diagnostic suffix is present or when
  whitespace is U+00A0. Introduces stripDiagnosticAnnotation +
  normalizeWhitespace + canonicalLabel mirroring the regex from
  src/tools/app-handle-alert.ts so the assertion compares against
  canonicalized labels.

Verified locally:
- Default-skip run passes 1 test with the structured warning
- ESLint clean

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9f1599c52

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +79 to +80
function normalizeWhitespace(label: string): string {
return label.replace(/\s+/gu, ' ').trim();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize U+2060 separators in canonical button labels

canonicalLabel() currently relies on label.replace(/\s+/gu, ' '), but JavaScript \s does not match U+2060 WORD JOINER. collectVisibleButtonLabels can emit labels containing U+2060 (it is explicitly treated as fancy whitespace in src/tools/app-handle-alert-labels.ts), so a value like "허용⁠안⁠함 (norm: 허용 안 함)" is stripped to "허용⁠안⁠함" and never equals DISMISS_LABEL. In that case this live suite reports a false regression even though the expected ko-KR button is present.

Useful? React with 👍 / 👎.

…odex P2)

Address chatgpt-codex-connector P2 on PR #692 (commit d9f1599, line 80):
JavaScript's `\s` regex class does NOT match U+2060 WORD JOINER, even
though `src/tools/app-handle-alert-labels.ts` explicitly treats it as
fancy whitespace alongside U+00A0 / U+202F / U+2007 / U+2028 / U+2029.
A SpringBoard label like "허용⁠안⁠함" (with U+2060 between syllables)
would slip past `\s+` normalization untouched and never equal the
plain ASCII-spaced constant `허용 안 함`, producing a false-regression
report on a passing behavior.

Mirror the FANCY_WHITESPACE codepoint set from the source file 1:1 and
strip those characters before applying the standard `\s+` collapse.
The escape sequence form is used so the regex character class is
unambiguous in source — avoids the editor-level invisibility of literal
control characters.

Verified locally:
- Default-skip run passes 1 test
- ESLint clean

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@shaun0927
Copy link
Copy Markdown
Owner Author

Bot review iteration summary

All four review items from the initial round on 319edbc0 are addressed in the two follow-up commits on this branch:

Item Source Commit
simctl reset failure should throw, not warn (gemini@:204) d9f1599c line 244: throw new Error(\[issue-660] ${reset.reason}`)`
Empty tool body should throw (gemini@:232) d9f1599c line 271: throw new Error('[issue-660] tool handler returned no body')
Missing permission sheet should throw (gemini@:246) d9f1599c line 284: throw new Error(...)
visibleButtons may carry (norm: …) annotation — strip before comparing (codex P1@:253) d9f1599c stripDiagnosticAnnotation + canonicalLabel mirror the regex from src/tools/app-handle-alert.ts
\s regex misses U+2060 WORD JOINER (codex P2@:80, new on d9f1599c) b71c6c95 FANCY_WHITESPACE = /[   ⁠

]/g mirrors app-handle-alert-labels.ts 1:1

The skip-with-warn pattern is preserved for the default no-opt-in path only. Once OPENSAFARI_KOKR_PUSH_DIALOG=1 flips, every environmental failure throws.

Re-posted bot comments on b71c6c95 cite the same line numbers as the addressed items but the actual code at those lines is the throw / canonicalize form requested — likely a bot artifact from re-evaluating the new commit. Confirmed by reading the file post-b71c6c95.

CI: lint + test + build all green on b71c6c95.

🤖 Generated with Claude Code

@shaun0927 shaun0927 merged commit 5bc29a3 into develop Apr 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant