Sitelet https://github.com/unoplatform/uno/pull/24965
Skip to content

ci: Move iOS runtime tests off macOS-14 (backport 6.7) - #24965

Open
MartinZikmund wants to merge 10 commits into
release/stable/6.7from
dev/mazi/ci-macos14-off-6.7
Open

MartinZikmund wants to merge 10 commits into
release/stable/6.7from
dev/mazi/ci-macos14-off-6.7

Conversation

@MartinZikmund

@MartinZikmund MartinZikmund commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

GitHub Issue: part of #24959

Backport of #24680 and #24961 to release/stable/6.7, in one PR.

PR Type:

🏗️ Build or CI related changes

What changed? 🚀

The hosted macOS-14 image is retired on 2 Nov 2026, with outage windows from 5 Oct. On this branch the iOS Skia runtime tests still ran on it (macOSVMImage_UITests / Xcode 15.3), and every other Mac job ran on macOS-15. This branch has no tvOS stage.

  • No macOS-15 either: the build image moves from macOS-15 to macOS-26. The DevServer CLI template-test jobs that hardcoded macOS-15 move too.
  • Xcode: builds stay on Xcode 26.1.1 (also on macOS-26), because .NET for iOS 26.1 refuses any other Xcode. macOS-26 doesn't preinstall the iOS 26.1 simulator runtime, and the build jobs need it (actool won't compile asset catalogs without a runtime that matches the SDK), so Select Xcode downloads it there, as it already did when missing. The iOS runtime-test shards select Xcode 26.6 (xCodeRootSimulator), whose iOS 26.5 runtime is preinstalled, so they skip that download. An app built with the 26.1 SDK runs on the newer simulator.
  • The iOS shards run on the build image (macOS-26, iOS 26.5 simulator).
  • The macOS desktop runtime-test job timeout goes from 60 to 120 min: the 6.x suite already took 26-55 min on macOS-15 and ran past 60 on macOS-26.
  • ios-uitest-run.sh uses the simulator runtime that ships with the selected Xcode instead of the hardcoded iOS-17-5, creates iPad Pro (12.9-inch) (6th generation) when the image lacks it (same screen size as before), and installs with xcrun simctl install (one retry). idb and its Homebrew tap / Python pins are gone.
  • macOSVMImage_UITests, xCodeRoot_iOS_UITests and the vmMacImageTest / vmImageTest / xCodeRootTest template parameters are removed. The disabled native iOS stage and TestFlight now take the build image and Xcode.

Also regenerates FontFallbackMaps.skia.cs. The upstream Noto data moved, so Setup - Font Fallback Maps Validation fails on every 6.7 PR (master and 6.8 regenerate it on a schedule; 6.7 doesn't).

Also cherry-picks caa909b46c8 from servicing/6.8, which ignores RealAppLaunchIntegrationTests.WhenRealAppBuiltAndRunWithDevServer_RealConnectionEstablished. Uno.Templates@*-* now scaffolds MyApp on 7.0 packages, which a 6.x DevServer can't connect to, so the test fails on release/stable/6.7 itself (build 236027).

Also skips the five WebView2 ExecuteScriptAsync runtime tests on SkiaMacOS, next to their existing iOS/Win32 #11997 exclusion. On the macOS-26 image they segfault the whole Desktop Skia macOS process on 6.x (3 out of 3 runs on 6.7), so every later test is lost. Master passes them on macOS-26, and backporting the #24112 native-peer fix did not help, so the root cause is 6.x-specific and needs a Mac to chase.

The iOS multi-window scene fix from #24680 isn't needed here: this branch doesn't support multiple windows on iOS (SupportsMultipleWindows is false), so closing a window can't background the app.

Risk: the simulator moves from iOS 17.5 to iOS 26.5, so outcomes can shift. This PR's CI run is the check.

PR Checklist ✅

🤖 Generated with Claude Code

https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa

MartinZikmund and others added 2 commits October 3, 2026 11:37
Backport of #24680 (CI part) to release/stable/6.7. The macOS-14 hosted image is retired on 2 Nov 2026. The shards now use the build image and Xcode, take the simulator runtime that Xcode ships, create the 12.9-inch iPad when the image lacks it, and install with xcrun simctl instead of idb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa
Backport of #24961 (cleanup part) to release/stable/6.7. Nothing runs on macOSVMImage_UITests / xCodeRoot_iOS_UITests anymore; the disabled native iOS stage and TestFlight now take the build image and Xcode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa
Copilot AI balanced review requested due to automatic review settings October 3, 2026 09:38
@MartinZikmund MartinZikmund added the area/build Categorizes an issue or PR as relevant to build infrastructure label Oct 3, 2026
@MartinZikmund MartinZikmund self-assigned this Oct 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 3 Medium severity · 1 Low severity

Open (4)
What changed in this PR

Moves iOS Skia runtime tests off the retiring macOS-14 image by aligning UI test execution with the standard macOS image/Xcode and simplifying the simulator install path.

Changes:

  • Switch ios-uitest-run.sh from idb-based installs and hardcoded iOS runtime to Xcode-selected runtime discovery + simctl install (with one retry).
  • Remove macOS-14 / Xcode 15.3 “UITest” image parameters from Azure DevOps templates and route iOS test jobs to the build image/Xcode.
  • Drop macOSVMImage_UITests and xCodeRoot_iOS_UITests variables from the main pipeline variables.
File Description
build/​test-scripts/​ios-uitest-run.sh Updates simulator runtime/device resolution and app install flow to avoid idb and hardcoded iOS 17.5 runtime.
build/​ci/​tests/​.azure-devops-tests--skia.yml Removes test-only macOS/Xcode parameters and uses the shared build image/Xcode for iOS Skia runtime tests.
build/​ci/​tests/​.azure-devops-tests-ios-native.yml Routes native iOS test jobs to the shared image/Xcode settings instead of test-only parameters.
build/​ci/​publish/​.azure-devops-publish-ios-testflight.yml Removes test-only Xcode root parameter to align publishing with build Xcode selection.
build/​ci/​.azure-devops-stages.yml Stops supplying macOS-14/Xcode 15.3 test-only parameters into downstream templates.
.vsts-ci.yml Removes macOS-14/Xcode 15.3 test-only variables from pipeline configuration.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +173 to +180
UNO_UITEST_SIMULATOR_VERSION=$(xcrun simctl list runtimes --json | jq -r --arg ver "$IOS_SDK_VERSION" '
.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\.iOS")) and (.version | startswith($ver))))
| sort_by(.version | split(".") | map(tonumber? // 0))
| .[-1].identifier // empty')

if [ -z "$UNO_UITEST_SIMULATOR_VERSION" ]; then
echo "##vso[task.logissue type=error]UNOBLD008: No iOS $IOS_SDK_VERSION simulator runtime is available on this agent."
Comment on lines +187 to +194
find_ios_device() {
xcrun simctl list devices --json | jq -r --arg sim "$UNO_UITEST_SIMULATOR_VERSION" --arg name "$UNO_UITEST_SIMULATOR_NAME" '
(.devices[$sim] // [])
| map(select(.name == $name and .isAvailable == true))
| .[0].udid // empty'
}

# Check for the presence of idb, and install it if it's not present
# NOTE: fb-idb currently breaks under Python 3.14 (asyncio get_event_loop change),
# so we pin fb-idb to Python 3.12 to avoid "There is no current event loop in thread 'MainThread'".
# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)
Comment on lines +215 to +220
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

export DEVICELIST_FILEPATH=$LOG_FILEPATH/DeviceList-$LOG_PREFIX.json
echo "Listing iOS simulators to $DEVICELIST_FILEPATH"
xcrun simctl list devices --json > $DEVICELIST_FILEPATH


export UNO_UITEST_SIMULATOR_VERSION="com.apple.CoreSimulator.SimRuntime.iOS-17-5"
export UNO_UITEST_SIMULATOR_NAME="iPad Pro (12.9-inch) (6th generation)"
export UNO_UITEST_SIMULATOR_NAME="${UNO_UITEST_SIMULATOR_NAME:=iPad Pro (12.9-inch) (6th generation)}"
Moves the remaining macOS-15 jobs to macOS-26, as on master. macOS-26 does not ship the iOS 26.1 simulator runtime that Xcode 26.1.1 needs, so Xcode moves to 26.6 (iOS 26.5 runtime preinstalled).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 3m 13s —— View job


PR Review

Verdict: fix-first — the CI YAML cleanup is correct and complete, but ios-uitest-run.sh has three medium reliability issues that are likely to cause flaky shard failures on freshly provisioned agents.

3 medium issues, 0 blocking:

# Severity Location Issue
1 Medium ios-uitest-run.sh:177 startswith($IOS_SDK_VERSION) version match can miss a valid runtime when SDK and runtime versions diverge at patch level
2 Medium ios-uitest-run.sh:194 CoreSimulator startup poll removed — single find_ios_device() call with no retry reintroduces the flakiness the old loop guarded against
3 Medium ios-uitest-run.sh:215 UITEST_IOSDEVICE_DATA_PATH gets the literal string "null" instead of empty when the UDID lookup misses

Cross-cutting notes:

  • CI YAML cleanup is solid. Removing macOSVMImage_UITests / xCodeRoot_iOS_UITests from all call sites and template parameter lists is consistent and correct. The previously undeclared vmImageTest parameter in ios-native.yml (which silently expanded to empty) is now correctly replaced with the declared vmImage.
  • idb removal is correct. The tap breakage and 6-minute install cost were genuine blockers; xcrun simctl install is the right replacement for Xcode 26.
  • UnoTargetFrameworkOverride=net9.0-ios18.0 is unchanged — apps targeting iOS 18 minimum run forward-compatibly on iOS 26 simulators, so this is fine.
  • All three issues are concentrated in ios-uitest-run.sh and can be addressed without touching the CI YAML files.

.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\.iOS")) and (.version | startswith($ver))))
| sort_by(.version | split(".") | map(tonumber? // 0))
| .[-1].identifier // empty')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — fragile version match that can produce a false "no runtime" failure.

xcrun --sdk iphonesimulator --show-sdk-version returns a three-part string such as "26.1.0" or a two-part string such as "26.1" depending on the Xcode version. startswith($ver) then matches the runtime .version field, which is always three-part (e.g. "26.1.0"). That is fine when the two strings agree, but it breaks if they diverge even slightly:

  • SDK → "26.1", runtime .version → "26.0.0" → no match → false failure.
  • SDK → "26.1.0", runtime .version → "26.1.1" → startswith("26.1.0") fails → drops a valid runtime.

A more robust approach is to compare only major+minor, which is the meaningful compatibility boundary for simulator runtimes:

Suggested change
| .[-1].identifier // empty')
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | split(".")[0:2] | join(".") == ($ver | split(".")[0:2] | join(".")))))

Alternatively, strip both strings to major.minor before the comparison. Either way the error message below should include the full list of discovered runtimes so the build log explains why the match failed.

# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — removed the CoreSimulator startup poll, reintroducing a source of intermittent flakiness.

The old code looped with sleep 5 until simctl list returned the device. That guard existed because CoreSimulator can take a few seconds to become responsive on a freshly provisioned agent, during which simctl list returns an empty or incomplete device list.

The new code calls find_ios_device() once. If CoreSimulator isn't ready, it returns empty → the code falls into the "create" path → xcrun simctl create may fail or silently duplicate a device, and the agent image already pre-creates this model.

Consider wrapping the initial lookup in a short bounded retry before falling through to device creation, e.g.:

for _attempt in 1 2 3; do
    UITEST_IOSDEVICE_ID=$(find_ios_device)
    [ -n "$UITEST_IOSDEVICE_ID" ] && break
    echo "CoreSimulator not ready yet (attempt $_attempt/3), waiting 5s"
    sleep 5
done

If it's still empty after retries, then proceed with creation — that path is correct for images that genuinely don't ship the device model.

fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling.

jq -r returns the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create and the device doesn't yet appear in the flat devices[][] scan. UITEST_IOSDEVICE_DATA_PATH is later used as a filesystem path; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')

Add a check after the assignment:

if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
    echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 2m 7s —— View job


PR Review

Verdict: fix-first — the CI YAML cleanup is correct and complete, but ios-uitest-run.sh still has three medium-reliability issues that are likely to cause flaky shard failures on freshly provisioned agents. These were flagged in the previous review pass and remain unaddressed after the macOS-26 / Xcode 26.6 follow-up commits.

3 medium issues, 0 blocking:

# Severity Location Issue
1 Medium ios-uitest-run.sh:175 startswith($ver) version match rejects a valid runtime when SDK and runtime patch versions diverge
2 Medium ios-uitest-run.sh:194 CoreSimulator startup poll removed — single find_ios_device() call with no retry reintroduces the flakiness the old loop guarded against
3 Medium ios-uitest-run.sh:215 UITEST_IOSDEVICE_DATA_PATH gets the literal string "null" instead of empty when the UDID lookup misses

Cross-cutting notes:

  • CI YAML cleanup is solid. Removing macOSVMImage_UITests / xCodeRoot_iOS_UITests from all call sites and template parameter lists is consistent and correct. The vmMacImageTest / xCodeRootTest parameters are cleanly excised from ios-skia, ios-native, and the skia-stages fan-out. The previously undeclared vmImageTest parameter in ios-native.yml (which silently expanded to empty) is now replaced with the declared vmImage.

  • macOS-15 → macOS-26 / Xcode_26.1.1 → Xcode_26.6 uplift in the two most recent commits is consistent across .vsts-ci.yml and the two matrix entries in .azure-devops-tests-templates.yml.

  • idb removal is correct. The tap breakage and 6-minute install cost were genuine blockers; xcrun simctl install with a single retry is the right replacement for Xcode 26.

  • All three issues are concentrated in ios-uitest-run.sh and can be addressed without touching any YAML files.

  • Gather context

  • Read changed files

  • Analyze findings

  • Post inline comments

  • Update summary

Comment thread build/test-scripts/ios-uitest-run.sh Outdated
IOS_SDK_VERSION=$(xcrun --sdk iphonesimulator --show-sdk-version)
UNO_UITEST_SIMULATOR_VERSION=$(xcrun simctl list runtimes --json | jq -r --arg ver "$IOS_SDK_VERSION" '
.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\.iOS")) and (.version | startswith($ver))))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — startswith($ver) can produce a false "no runtime" failure.

xcrun --sdk iphonesimulator --show-sdk-version returns a two-part string ("26.1") on some Xcode versions and three-part ("26.1.0") on others. The runtime .version field from simctl list runtimes is always three-part. When $ver is "26.1.0" and the installed runtime is at patch "26.1.1", startswith("26.1.0") is false — a perfectly valid runtime gets rejected.

The meaningful compatibility boundary for simulator runtimes is major.minor. Compare only that:

Suggested change
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\.iOS")) and (.version | startswith($ver))))
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | split(".")[0:2] | join(".") == ($ver | split(".")[0:2] | join(".")))))

Also worth adding the discovered runtimes to the error message below so the build log explains why the match failed.

# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — single find_ios_device() call reintroduces CoreSimulator startup flakiness.

The old code polled in a while true loop with sleep 5 until simctl list returned the device, specifically because CoreSimulator can take a few seconds to become responsive on a freshly provisioned agent. The new code calls find_ios_device() exactly once: if CoreSimulator isn't ready, the result is empty, the code enters the "create" path, and simctl create can silently duplicate the pre-created device — or fail outright.

Add a short bounded retry before falling through to device creation:

Suggested change
UITEST_IOSDEVICE_ID=$(find_ios_device)
for _cs_attempt in 1 2 3; do
UITEST_IOSDEVICE_ID=$(find_ios_device)
[ -n "$UITEST_IOSDEVICE_ID" ] && break
echo "CoreSimulator not ready yet (attempt $_cs_attempt/3), retrying in 5s…"
sleep 5
done

If UITEST_IOSDEVICE_ID is still empty after retries, the existing creation path below handles it correctly — that path is valid for images that genuinely don't pre-create the device model.

fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling.

jq -r emits the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create and the device doesn't yet appear in the flat devices[][] scan. UITEST_IOSDEVICE_DATA_PATH is used as a filesystem path later; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')
if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

The upstream Noto font data moved, so the Font Fallback Maps Validation stage fails on every release/stable/6.7 PR. master and servicing/6.8 regenerate these on a schedule; 6.7 does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa
@github-actions github-actions Bot added the area/skia ✏️ Categorizes an issue or PR as relevant to Skia label Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 2m 33s —— View job


PR Review

Verdict: fix-first — the CI YAML cleanup is correct and complete, but ios-uitest-run.sh still has three medium-reliability issues that are likely to cause flaky shard failures on freshly provisioned agents. These were flagged in the previous two review passes and remain unaddressed.

3 medium issues, 0 blocking:

# Severity Location Issue
1 Medium ios-uitest-run.sh:175 startswith($ver) rejects a valid runtime when SDK and runtime patch versions diverge
2 Medium ios-uitest-run.sh:194 CoreSimulator startup poll removed — single find_ios_device() call reintroduces flakiness the old loop guarded against
3 Medium ios-uitest-run.sh:215 UITEST_IOSDEVICE_DATA_PATH gets the literal string "null" instead of empty when the UDID lookup misses

Cross-cutting notes:

  • CI YAML cleanup is solid. macOSVMImage_UITests / xCodeRoot_iOS_UITests removed from .vsts-ci.yml and all call sites. vmMacImageTest / xCodeRootTest cleanly excised from ios-skia-build, ios-skia, and the skia-stages fan-out. The previously undeclared vmImageTest parameter in ios-native.yml (which silently expanded to empty) is now correctly replaced with the declared vmImage. The two hardcoded macOS-15 entries in .azure-devops-tests-templates.yml are updated to macOS-26.

  • macOS-26 / Xcode 26.6 uplift is consistent across .vsts-ci.yml and both matrix entries in the templates file.

  • idb removal is correct. The tap breakage and 6-minute install cost were genuine blockers; xcrun simctl install with a single retry is the right replacement for Xcode 26.

  • UnoTargetFrameworkOverride=net9.0-ios18.0 is unchanged — apps targeting iOS 18 minimum run forward-compatibly on iOS 26.5 simulators, so this is fine.

  • All three issues are concentrated in ios-uitest-run.sh and can be addressed without touching any YAML files.

  • Gather context

  • Read changed files

  • Analyze findings

  • Post inline comments

  • Update summary

Comment thread build/test-scripts/ios-uitest-run.sh Outdated
IOS_SDK_VERSION=$(xcrun --sdk iphonesimulator --show-sdk-version)
UNO_UITEST_SIMULATOR_VERSION=$(xcrun simctl list runtimes --json | jq -r --arg ver "$IOS_SDK_VERSION" '
.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\.iOS")) and (.version | startswith($ver))))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — startswith($ver) can produce a false "no runtime" failure.

xcrun --sdk iphonesimulator --show-sdk-version returns a two-part string ("26.1") on some Xcode versions and a three-part string ("26.1.0") on others. The runtime .version field from simctl list runtimes is always three-part. When $ver is "26.1.0" and the installed runtime is at patch "26.1.1", startswith("26.1.0") is false — a valid runtime gets rejected and the job fails with UNOBLD008.

The meaningful compatibility boundary for simulator runtimes is major.minor. Narrow the comparison to that:

Suggested change
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\.iOS")) and (.version | startswith($ver))))
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | split(".")[0:2] | join(".") == ($ver | split(".")[0:2] | join(".")))))

Also worth surfacing the discovered runtimes in the error message below (line 181) so the build log explains why the match failed.

# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — single find_ios_device() call reintroduces CoreSimulator startup flakiness.

The old code polled with while true; do sleep 5; done specifically because CoreSimulator can take a few seconds to become responsive on a freshly provisioned agent. The new code calls find_ios_device() exactly once — if CoreSimulator isn't ready, the result is empty and execution falls through to the device-creation path, which may silently duplicate a pre-created device or fail outright.

A short bounded retry before the creation fallback preserves the guard without an unbounded loop:

Suggested change
UITEST_IOSDEVICE_ID=$(find_ios_device)
for _cs_attempt in 1 2 3; do
UITEST_IOSDEVICE_ID=$(find_ios_device)
[ -n "$UITEST_IOSDEVICE_ID" ] && break
echo "CoreSimulator not ready yet (attempt $_cs_attempt/3), retrying in 5s…"
sleep 5
done

If UITEST_IOSDEVICE_ID is still empty after retries, the existing creation path below is the correct fallback — that path is valid for images that genuinely don't pre-create the device model.

fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling.

jq -r emits the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create and the device doesn't yet appear in the flat devices[][] scan. UITEST_IOSDEVICE_DATA_PATH is used as a filesystem path later; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')
if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

Uno.Templates@*-* now resolves to 7.0.0-dev.17, so MyApp is built on
7.0 packages and the locally built 6.x RemoteControl client never
connects. Mirrors the master ignore (37dc650).

Refs #24800

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RHVD5sbb5nMHzf8Lu24gZ
(cherry picked from commit caa909b)
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 1m 59s —— View job


PR Review

Verdict: fix-first — the CI YAML cleanup is correct and complete, but ios-uitest-run.sh still has three medium-reliability issues that are likely to cause flaky shard failures on freshly provisioned agents. These have been flagged in every previous review pass and remain unaddressed after the macOS-26 / Xcode 26.6 follow-up commits.

3 medium issues, 0 blocking:

# Severity Location Issue
1 Medium ios-uitest-run.sh:175 startswith($ver) rejects a valid runtime when SDK and runtime patch versions diverge
2 Medium ios-uitest-run.sh:194 Single find_ios_device() call reintroduces the CoreSimulator startup flakiness the old poll loop guarded against
3 Medium ios-uitest-run.sh:215 UITEST_IOSDEVICE_DATA_PATH gets the literal string "null" instead of empty when the UDID lookup misses

Each issue has a code suggestion attached to its inline comment — they are independent one-to-three line fixes.

Cross-cutting notes:

  • CI YAML cleanup is solid. macOSVMImage_UITests / xCodeRoot_iOS_UITests removed from all call sites. vmMacImageTest / xCodeRootTest cleanly excised from ios-skia-build, ios-skia, and the skia-stages fan-out. The previously undeclared vmImageTest parameter in ios-native.yml (which silently expanded to empty) is replaced with the declared vmImage. The two hardcoded macOS-15 entries in .azure-devops-tests-templates.yml are updated to macOS-26.

  • macOS-26 / Xcode 26.6 uplift is consistent across .vsts-ci.yml and both matrix entries in the templates file.

  • idb removal is correct. The tap breakage and 6-minute install cost were genuine blockers; xcrun simctl install with a single retry is the right replacement for Xcode 26.

  • UnoTargetFrameworkOverride=net9.0-ios18.0 is unchanged — apps targeting iOS 18 minimum run forward-compatibly on iOS 26.5 simulators, so this is fine.

  • FontFallbackMaps.skia.cs regeneration is a correct housekeeping fix for the broken upstream Noto data path.

  • All three issues are concentrated in ios-uitest-run.sh and can be addressed without touching any YAML files.

  • Gather context

  • Read changed files

  • Analyze findings

  • Post inline comments

  • Update summary

Comment thread build/test-scripts/ios-uitest-run.sh Outdated
IOS_SDK_VERSION=$(xcrun --sdk iphonesimulator --show-sdk-version)
UNO_UITEST_SIMULATOR_VERSION=$(xcrun simctl list runtimes --json | jq -r --arg ver "$IOS_SDK_VERSION" '
.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\.iOS")) and (.version | startswith($ver))))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — startswith($ver) can produce a false "no runtime" failure.

xcrun --sdk iphonesimulator --show-sdk-version returns a two-part string ("26.1") on some Xcode versions and three-part ("26.1.0") on others. The runtime .version from simctl list runtimes is always three-part. When $ver is "26.1.0" and the installed runtime is "26.1.1", startswith("26.1.0") is false — a valid runtime gets rejected and the job fails with UNOBLD008.

The meaningful compatibility boundary for simulator runtimes is major.minor. Compare only that:

Suggested change
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\.iOS")) and (.version | startswith($ver))))
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | split(".")[0:2] | join(".") == ($ver | split(".")[0:2] | join(".")))))

Also worth surfacing the discovered runtimes in the error message (line 180) so the build log explains why the match failed.

# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — single find_ios_device() call reintroduces CoreSimulator startup flakiness.

The old code polled with while true; do sleep 5; done specifically because CoreSimulator can take a few seconds to become responsive on a freshly provisioned agent. The new code calls find_ios_device() exactly once — if CoreSimulator isn't ready, the result is empty, execution falls through to the device-creation path, and simctl create can silently duplicate a pre-created device or fail outright.

A short bounded retry before the creation fallback preserves the guard without an unbounded loop:

Suggested change
UITEST_IOSDEVICE_ID=$(find_ios_device)
for _cs_attempt in 1 2 3; do
UITEST_IOSDEVICE_ID=$(find_ios_device)
[ -n "$UITEST_IOSDEVICE_ID" ] && break
echo "CoreSimulator not ready yet (attempt $_cs_attempt/3), retrying in 5s…"
sleep 5
done

If UITEST_IOSDEVICE_ID is still empty after retries, the existing creation path below is the correct fallback — it's valid for images that genuinely don't pre-create the device model.

fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling.

jq -r emits the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create and the device doesn't yet appear in the flat devices[][] scan. UITEST_IOSDEVICE_DATA_PATH is used as a filesystem path later; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')
if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

.NET for iOS 26.1 refuses any Xcode but 26.1 ("requires Xcode 26.1. The current version of Xcode is 26.6"), so builds stay on Xcode 26.1.1, which macOS-26 also ships. That image has no iOS 26.1 simulator runtime, so build-only jobs skip the runtime check in Select Xcode, and the iOS runtime-test shards select Xcode 26.6, whose iOS 26.5 runtime is preinstalled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa
fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling (still unaddressed).

jq -r emits the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create and the device doesn't yet appear in the flat devices[][] scan. UITEST_IOSDEVICE_DATA_PATH is used as a filesystem path later; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')
if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-24965/wasm-skia-net9/index.html

jq rejects "\." in a string literal, so every iOS shard aborted while resolving the simulator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 2m 38s —— View job


PR Review

Verdict: fix-first — the CI YAML cleanup is correct and complete, but ios-uitest-run.sh still has three medium-reliability issues. These have now been flagged in every review pass (7 total) and all three have one-click code suggestions attached to their inline comments — please apply them before merge.

3 medium issues, 0 blocking:

# Severity Location Issue
1 Medium ios-uitest-run.sh:175 startswith($ver) rejects a valid runtime when SDK and runtime patch versions diverge — compare major.minor only
2 Medium ios-uitest-run.sh:194 Single find_ios_device() call reintroduces the CoreSimulator startup flakiness the old poll loop guarded against — add a 3-attempt bounded retry
3 Medium ios-uitest-run.sh:215 UITEST_IOSDEVICE_DATA_PATH gets the literal string "null" instead of empty when the UDID lookup misses — use // empty and emit UNOBLD009 warning

Each issue has a one-click code suggestion attached. All three are independent and confined to ios-uitest-run.sh; no YAML files need to change.

Cross-cutting notes (unchanged):

  • CI YAML cleanup is solid. macOSVMImage_UITests / xCodeRoot_iOS_UITests removed from all call sites. vmMacImageTest / xCodeRootTest cleanly excised. The two hardcoded macOS-15 entries in .azure-devops-tests-templates.yml are updated to macOS-26.

  • macOS-26 / Xcode 26.6 uplift is consistent across .vsts-ci.yml and both matrix entries in the templates file.

  • idb removal is correct. xcrun simctl install with a single retry is the right replacement for Xcode 26.

  • RealAppLaunchIntegrationTests [Ignore] is the right approach — Uno.Templates@*-* scaffolds against 7.0 packages that a 6.x DevServer can't connect to.

  • FontFallbackMaps.skia.cs regeneration is a correct housekeeping fix.

  • Gather context

  • Read changed files

  • Analyze findings

  • Post inline comments

  • Update summary

IOS_SDK_VERSION=$(xcrun --sdk iphonesimulator --show-sdk-version)
UNO_UITEST_SIMULATOR_VERSION=$(xcrun simctl list runtimes --json | jq -r --arg ver "$IOS_SDK_VERSION" '
.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | startswith($ver))))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — fragile version match (still unaddressed after 6+ review passes).

xcrun --sdk iphonesimulator --show-sdk-version can return either a two-part ("26.5") or three-part ("26.5.0") string depending on the Xcode version. The runtime .version from simctl list runtimes is always three-part. If $ver is "26.5.0" and the installed runtime is "26.5.1", startswith("26.5.0") is false — a perfectly valid runtime gets rejected and the job fails with UNOBLD008.

The meaningful compatibility boundary for simulator runtimes is major.minor. Compare only that:

Suggested change
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | startswith($ver))))
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | split(".")[0:2] | join(".") == ($ver | split(".")[0:2] | join(".")))))

# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — single find_ios_device() call reintroduces CoreSimulator startup flakiness (still unaddressed after 6+ review passes).

The old code polled with while true; do sleep 5; done specifically because CoreSimulator can take a few seconds to become responsive on a freshly provisioned agent. The new code calls find_ios_device() exactly once — if CoreSimulator isn't ready, the result is empty, execution falls through to the device-creation path, and simctl create can silently duplicate a pre-created device or fail outright.

A short bounded retry before the creation fallback preserves the guard without an unbounded loop:

Suggested change
UITEST_IOSDEVICE_ID=$(find_ios_device)
for _cs_attempt in 1 2 3; do
UITEST_IOSDEVICE_ID=$(find_ios_device)
[ -n "$UITEST_IOSDEVICE_ID" ] && break
echo "CoreSimulator not ready yet (attempt $_cs_attempt/3), retrying in 5s…"
sleep 5
done

If UITEST_IOSDEVICE_ID is still empty after retries, the existing creation path below is the correct fallback — it's valid for images that genuinely don't pre-create the device model.

fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling (still unaddressed after 6+ review passes).

jq -r emits the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create and the device doesn't yet appear in the flat devices[][] scan. UITEST_IOSDEVICE_DATA_PATH is used as a filesystem path later; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')
if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-24965/wasm-skia-net9/index.html

The 6.x suite took 26-55 min on macOS-15 against a 60 min job timeout, and ran past it on macOS-26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 2m 3s —— View job


PR Review

Verdict: fix-first — the CI YAML cleanup is correct and complete, but ios-uitest-run.sh still has three medium-reliability issues that have been flagged in every review pass and remain unaddressed. Each has a one-click code suggestion attached to its inline comment.

3 medium issues, 0 blocking:

# Severity Location Issue
1 Medium ios-uitest-run.sh:175 startswith($ver) rejects a valid runtime when SDK and runtime patch versions diverge — compare major.minor only
2 Medium ios-uitest-run.sh:194 Single find_ios_device() call reintroduces the CoreSimulator startup flakiness the old poll loop guarded against — add a 3-attempt bounded retry
3 Medium ios-uitest-run.sh:215 UITEST_IOSDEVICE_DATA_PATH gets the literal string "null" instead of empty when the UDID lookup misses — use // empty and emit UNOBLD009 warning

Cross-cutting notes (unchanged from previous passes):

  • CI YAML cleanup is solid. macOSVMImage_UITests / xCodeRoot_iOS_UITests removed from all call sites. vmMacImageTest / xCodeRootTest cleanly excised from ios-skia-build, ios-skia, and the skia-stages fan-out. The two hardcoded macOS-15 entries in .azure-devops-tests-templates.yml are updated to macOS-26.

  • macOS-26 / Xcode 26.6 uplift is consistent across .vsts-ci.yml and both matrix entries in the templates file.

  • idb removal is correct. The tap breakage and 6-minute install cost were genuine blockers; xcrun simctl install with a single retry is the right replacement for Xcode 26.

  • RealAppLaunchIntegrationTests [Ignore] is the right approach — Uno.Templates@*-* scaffolds against 7.0 packages that a 6.x DevServer can't connect to.

  • FontFallbackMaps.skia.cs regeneration is a correct housekeeping fix.

  • All three open issues are confined to ios-uitest-run.sh and can be addressed without touching any YAML files.

  • Gather context

  • Read changed files

  • Analyze findings

  • Post inline comments

  • Update summary

IOS_SDK_VERSION=$(xcrun --sdk iphonesimulator --show-sdk-version)
UNO_UITEST_SIMULATOR_VERSION=$(xcrun simctl list runtimes --json | jq -r --arg ver "$IOS_SDK_VERSION" '
.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | startswith($ver))))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — fragile version match that can produce a false UNOBLD008 failure (flagged in every review pass, still unaddressed).

xcrun --sdk iphonesimulator --show-sdk-version returns a two-part string ("26.5") on some Xcode versions and three-part ("26.5.0") on others. The runtime .version from simctl list runtimes is always three-part. When $ver is "26.5.0" and the installed runtime is "26.5.1", startswith("26.5.0") is false — a valid runtime is rejected and the job fails with UNOBLD008.

Compare only major.minor, which is the meaningful compatibility boundary:

Suggested change
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | startswith($ver))))
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | split(".")[0:2] | join(".") == ($ver | split(".")[0:2] | join(".")))))

# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — single find_ios_device() call reintroduces CoreSimulator startup flakiness (flagged in every review pass, still unaddressed).

The old code polled with while true; do sleep 5; done specifically because CoreSimulator can take a few seconds to become responsive on a freshly provisioned agent. The new code calls find_ios_device() exactly once — if CoreSimulator isn't ready, the result is empty, execution falls through to the device-creation path, and simctl create can silently duplicate a pre-created device or fail outright.

A bounded retry before the creation fallback preserves the guard without an unbounded loop:

Suggested change
UITEST_IOSDEVICE_ID=$(find_ios_device)
for _cs_attempt in 1 2 3; do
UITEST_IOSDEVICE_ID=$(find_ios_device)
[ -n "$UITEST_IOSDEVICE_ID" ] && break
echo "CoreSimulator not ready yet (attempt $_cs_attempt/3), retrying in 5s…"
sleep 5
done

If UITEST_IOSDEVICE_ID is still empty after retries, the existing creation path below is the correct fallback — valid for images that genuinely don't pre-create the device model.

fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling (flagged in every review pass, still unaddressed).

jq -r emits the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create. UITEST_IOSDEVICE_DATA_PATH is used as a filesystem path later; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')
if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-24965/wasm-skia-net9/index.html

@github-actions github-actions Bot added the platform/macos 🍏 Categorizes an issue or PR as relevant to the macOS platform label Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 1m 46s —— View job


PR Review

Verdict: fix-first — the CI YAML cleanup is correct and complete, but ios-uitest-run.sh still has three medium-reliability issues that have been flagged in every review pass and remain unaddressed. Each has a one-click code suggestion attached to its inline comment.

3 medium issues, 0 blocking:

# Severity Location Issue
1 Medium ios-uitest-run.sh:175 startswith($ver) rejects a valid runtime when SDK and runtime patch versions diverge — compare major.minor only
2 Medium ios-uitest-run.sh:194 Single find_ios_device() call reintroduces the CoreSimulator startup flakiness the old poll loop guarded against — add a 3-attempt bounded retry
3 Medium ios-uitest-run.sh:215 UITEST_IOSDEVICE_DATA_PATH gets the literal string "null" instead of empty when the UDID lookup misses — use // empty and emit UNOBLD009 warning

Cross-cutting notes (unchanged from previous passes):

  • CI YAML cleanup is solid. macOSVMImage_UITests / xCodeRoot_iOS_UITests removed from all call sites. vmMacImageTest / xCodeRootTest cleanly excised from ios-skia-build, ios-skia, and the skia-stages fan-out. The two hardcoded macOS-15 entries in .azure-devops-tests-templates.yml are updated to macOS-26.

  • macOS-26 / Xcode 26.6 uplift is consistent across .vsts-ci.yml and both matrix entries in the templates file.

  • idb removal is correct. The tap breakage and 6-minute install cost were genuine blockers; xcrun simctl install with a single retry is the right replacement for Xcode 26.

  • RealAppLaunchIntegrationTests [Ignore] is the right approach — Uno.Templates@*-* scaffolds against 7.0 packages that a 6.x DevServer can't connect to.

  • FontFallbackMaps.skia.cs regeneration is a correct housekeeping fix.

  • All three open issues are confined to ios-uitest-run.sh and can be addressed without touching any YAML files.

  • Gather context

  • Read changed files

  • Analyze findings

  • Post inline comments

  • Update summary

IOS_SDK_VERSION=$(xcrun --sdk iphonesimulator --show-sdk-version)
UNO_UITEST_SIMULATOR_VERSION=$(xcrun simctl list runtimes --json | jq -r --arg ver "$IOS_SDK_VERSION" '
.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | startswith($ver))))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — fragile version match that can produce a false UNOBLD008 failure (still unaddressed after 8+ review passes).

xcrun --sdk iphonesimulator --show-sdk-version returns either a two-part string ("26.5") or a three-part string ("26.5.0") depending on the Xcode version. The runtime .version from simctl list runtimes is always three-part. When $ver is "26.5.0" and the installed runtime is at patch "26.5.1", startswith("26.5.0") is false — a valid runtime is rejected and the job fails with UNOBLD008.

The meaningful compatibility boundary for simulator runtimes is major.minor. Compare only that:

Suggested change
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | startswith($ver))))
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | split(".")[0:2] | join(".") == ($ver | split(".")[0:2] | join(".")))))

# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — single find_ios_device() call reintroduces CoreSimulator startup flakiness (still unaddressed after 8+ review passes).

The old code polled with while true; do sleep 5; done specifically because CoreSimulator can take a few seconds to become responsive on a freshly provisioned agent. The new code calls find_ios_device() exactly once — if CoreSimulator isn't ready, the result is empty, execution falls through to the device-creation path, and simctl create can silently duplicate a pre-created device or fail outright.

A bounded retry before the creation fallback preserves the guard without an unbounded loop:

Suggested change
UITEST_IOSDEVICE_ID=$(find_ios_device)
for _cs_attempt in 1 2 3; do
UITEST_IOSDEVICE_ID=$(find_ios_device)
[ -n "$UITEST_IOSDEVICE_ID" ] && break
echo "CoreSimulator not ready yet (attempt $_cs_attempt/3), retrying in 5s…"
sleep 5
done

If UITEST_IOSDEVICE_ID is still empty after retries, the existing creation path below is the correct fallback — valid for images that genuinely don't pre-create the device model.

fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling (still unaddressed after 8+ review passes).

jq -r emits the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create and the device doesn't yet appear in the flat devices[][] scan. UITEST_IOSDEVICE_DATA_PATH is used as a filesystem path later; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')
if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-24965/wasm-skia-net9/index.html

On the macOS-26 image these segfault the whole Desktop Skia macOS run on 6.x, in three out of three runs, taking every later test with it. They are already skipped on iOS and Win32 for #11997; master passes them, so the crash is 6.x-specific and needs a Mac to root-cause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018riLfyvT4wZYEh8iFjWkKa
@MartinZikmund
MartinZikmund force-pushed the dev/mazi/ci-macos14-off-6.7 branch from 61edc86 to 1936306 Compare October 3, 2026 23:01
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 2m 4s —— View job


PR Review

Verdict: fix-first — the CI YAML cleanup and the new 19363066 commit are correct, but ios-uitest-run.sh still has three medium-reliability issues that have been flagged in every review pass and remain unaddressed. Each has a one-click code suggestion attached to its inline comment.

3 medium issues, 0 blocking:

# Severity Location Issue
1 Medium ios-uitest-run.sh:175 startswith($ver) rejects a valid runtime when SDK and runtime patch versions diverge — compare major.minor only
2 Medium ios-uitest-run.sh:194 Single find_ios_device() call reintroduces the CoreSimulator startup flakiness the old poll loop guarded against — add a 3-attempt bounded retry
3 Medium ios-uitest-run.sh:215 UITEST_IOSDEVICE_DATA_PATH gets the literal string "null" instead of empty when the UDID lookup misses — use // empty and emit UNOBLD009 warning

New commit (19363066) is correct:

Adding RuntimeTestPlatforms.SkiaMacOS to the [PlatformCondition] exclude list for the five When_ExecuteScriptAsync_* tests in Given_WebView2.cs is the right approach. The comment clearly documents the reason (segfault on macOS-26) and links to #11997. Using [PlatformCondition] is more targeted than [Ignore] and is consistent with the existing exclusions in that file.

Cross-cutting notes (unchanged from previous passes):

  • CI YAML cleanup is solid. macOSVMImage_UITests / xCodeRoot_iOS_UITests removed from all call sites. vmMacImageTest / xCodeRootTest cleanly excised from ios-skia-build, ios-skia, and the skia-stages fan-out. The two hardcoded macOS-15 entries in .azure-devops-tests-templates.yml are updated to macOS-26.

  • macOS-26 / Xcode 26.6 uplift is consistent across .vsts-ci.yml and both matrix entries in the templates file.

  • idb removal is correct. xcrun simctl install with a single retry is the right replacement for Xcode 26.

  • RealAppLaunchIntegrationTests [Ignore] is the right approach — Uno.Templates@*-* scaffolds against 7.0 packages that a 6.x DevServer can't connect to.

  • FontFallbackMaps.skia.cs regeneration is a correct housekeeping fix.

  • All three open issues are confined to ios-uitest-run.sh and can be addressed without touching any YAML files.

  • Gather context

  • Read changed files

  • Analyze findings

  • Post inline comments

  • Update summary

IOS_SDK_VERSION=$(xcrun --sdk iphonesimulator --show-sdk-version)
UNO_UITEST_SIMULATOR_VERSION=$(xcrun simctl list runtimes --json | jq -r --arg ver "$IOS_SDK_VERSION" '
.runtimes
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | startswith($ver))))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — fragile version match that can produce a false UNOBLD008 failure (unaddressed in all previous review passes).

xcrun --sdk iphonesimulator --show-sdk-version returns a two-part string ("26.5") on some Xcode versions and three-part ("26.5.0") on others. The runtime .version field from simctl list runtimes is always three-part. When $ver is "26.5.0" and the installed runtime is "26.5.1", startswith("26.5.0") is false — a valid runtime is rejected and the job fails with UNOBLD008.

The meaningful compatibility boundary for simulator runtimes is major.minor. Compare only that:

Suggested change
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | startswith($ver))))
| map(select(.isAvailable == true and (.identifier | test("SimRuntime\\.iOS")) and (.version | split(".")[0:2] | join(".") == ($ver | split(".")[0:2] | join(".")))))

# Historical context: prior installs referenced an App Center issue/workaround.
# https://github.com/microsoft/appcenter/issues/2605#issuecomment-1854414963
export PATH=$PATH:~/.local/bin
UITEST_IOSDEVICE_ID=$(find_ios_device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — single find_ios_device() call reintroduces CoreSimulator startup flakiness (unaddressed in all previous review passes).

The old code polled with while true; do sleep 5; done specifically because CoreSimulator can take a few seconds to become responsive on a freshly provisioned agent. The new code calls find_ios_device() exactly once — if CoreSimulator isn't ready, the result is empty, execution falls through to the device-creation path, and simctl create can silently duplicate a pre-created device or fail outright.

A bounded retry before the creation fallback preserves the guard without an unbounded loop:

Suggested change
UITEST_IOSDEVICE_ID=$(find_ios_device)
for _cs_attempt in 1 2 3; do
UITEST_IOSDEVICE_ID=$(find_ios_device)
[ -n "$UITEST_IOSDEVICE_ID" ] && break
echo "CoreSimulator not ready yet (attempt $_cs_attempt/3), retrying in 5s…"
sleep 5
done

fi

export UITEST_IOSDEVICE_ID
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — literal "null" string can leak into downstream path handling (unaddressed in all previous review passes).

jq -r emits the string null (not empty) when .[0].dataPath is absent — for example if CoreSimulator is still settling after simctl create and the device doesn't yet appear in the flat devices[][] scan. UITEST_IOSDEVICE_DATA_PATH is used as a filesystem path later; a literal "null" component produces a confusing artifact-upload failure rather than a clear diagnostic.

Use // empty so jq emits nothing on a miss, then guard:

Suggested change
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath')
export UITEST_IOSDEVICE_DATA_PATH=$(xcrun simctl list devices --json | jq -r --arg udid "$UITEST_IOSDEVICE_ID" '[.devices[][] | select(.udid == $udid)][0].dataPath // empty')
if [ -z "$UITEST_IOSDEVICE_DATA_PATH" ]; then
echo "##vso[task.logissue type=warning]UNOBLD009: Could not resolve dataPath for device $UITEST_IOSDEVICE_ID"
fi

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-24965/wasm-skia-net9/index.html

@nventive-devops

Copy link
Copy Markdown
Contributor

The build 237295 did not find any UI Test snapshots differences.

@unodevops

Copy link
Copy Markdown
Contributor

⚠️⚠️ The build 237295 has failed on Uno.UI - CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/build Categorizes an issue or PR as relevant to build infrastructure area/skia ✏️ Categorizes an issue or PR as relevant to Skia platform/macos 🍏 Categorizes an issue or PR as relevant to the macOS platform

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants