mcpField
A
axiom-deep-link-debugging

axiom-deep-link-debugging

github.com/charleswiltgen测试与质量1.2k170 次安装今天更新
代码仓库

关于 axiom-deep-link-debugging

面向 iOS 开发者的调试深层链接实践指南,用于在模拟器中直接进入指定界面或状态。它涵盖 SwiftUI、NavigationStack 集成、参数校验,以及确保调试入口不会进入正式版本。

主要功能

  1. 01调试专用 URL Scheme
  2. 02按参数导航至指定界面
  3. 03链接配置应用状态
  4. 04集成现有导航架构
  5. 05限制调试代码进入正式版本

适用场景

  1. 01自动进入指定界面进行截图
  2. 02无需手动操作测试错误和空状态
  3. 03缩短界面修复后的验证流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-deep-link-debugging

安装后在 Claude Code 中输入/axiom-deep-link-debugging确认已加载。

详细介绍

深度链接调试

何时使用此技能

适用于:

  • 为模拟器测试添加仅用于调试的深度链接
  • 启用自动导航,以便截图或测试特定屏幕
  • 与 simulator-tester Agent 或 /axiom:screenshot 集成
  • 需要以编程方式导航,但不想实现生产环境的深度链接
  • 无需手动点击即可测试导航流程

不要用于:

  • 生产环境的深度链接(请改用 axiom-swiftui-nav 技能)
  • Universal Links 或 App Clips
  • 复杂的路由架构

示例提示词

1. “Claude Code 无法导航到用于测试的特定屏幕”

→ 添加仅用于调试的 URL scheme,以启用 xcrun simctl openurl 导航

2. “我想自动截取不同屏幕的截图”

→ 为每个屏幕创建可从模拟器调用的调试深度链接

3. “自动化测试需要设置特定的应用状态”

→ 添加既能导航又能配置状态的调试链接


注意事项——何时需要调试深度链接

如果你遇到以下任一情况,请添加调试深度链接:

测试阻碍:

  • ❌ “我必须手动点击 5 个屏幕才能测试这个功能”
  • ❌ “截屏功能无法显示我需要调试的屏幕”
  • ❌ “自动化测试必须经过复杂设置才能进入错误状态”

调试效率低下:

  • ❌ “我修复问题、重新构建、手动导航再检查——每次迭代都要花 3 分钟”
  • ❌ “Claude Code 无法导航到相应位置,所以没法直观验证修复结果”

解决方案:添加调试深度链接,让你(以及 Claude Code)可以直接跳转到任意屏幕,并配置任意状态。


实现

模式 1:基本调试 URL scheme(SwiftUI)

添加仅用于调试的 URL scheme,以便导航到各个屏幕。

import SwiftUI

struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                #if DEBUG
                .onOpenURL { url in
                    handleDebugURL(url)
                }
                #endif
        }
    }

    #if DEBUG
    private func handleDebugURL(_ url: URL) {
        guard url.scheme == "debug" else { return }

        // Route based on host
        switch url.host {
        case "settings":
            // Navigate to settings
            NotificationCenter.default.post(
                name: .navigateToSettings,
                object: nil
            )

        case "profile":
            // Navigate to profile
            let userID = url.queryItems?["id"] ?? "current"
            NotificationCenter.default.post(
                name: .navigateToProfile,
                object: userID
            )

        case "reset":
            // Reset app to initial state
            resetApp()

        default:
            print("⚠️ Unknown debug URL: \(url)")
        }
    }
    #endif
}

#if DEBUG
extension Notification.Name {
    static let navigateToSettings = Notification.Name("navigateToSettings")
    static let navigateToProfile = Notification.Name("navigateToProfile")
}

extension URL {
    var queryItems: [String: String]? {
        guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false),
              let items = components.queryItems else {
            return nil
        }
        return Dictionary(uniqueKeysWithValues: items.map { ($0.name, $0.value ?? "") })
    }
}
#endif

用法:

# From simulator
xcrun simctl openurl booted "debug://settings"
xcrun simctl openurl booted "debug://profile?id=123"
xcrun simctl openurl booted "debug://reset"

模式 2:与 NavigationPath 集成(iOS 16+)

将调试深度链接与 NavigationStack 集成,实现可靠的导航。

import SwiftUI

@MainActor
class DebugRouter: ObservableObject {
    @Published var path = NavigationPath()

    #if DEBUG
    func handleDebugURL(_ url: URL) {
        guard url.scheme == "debug" else { return }

        switch url.host {
        case "settings":
            path.append(Destination.settings)

        case "recipe":
            if let id = url.queryItems?["id"], let recipeID = Int(id) {
                path.append(Destination.recipe(id: recipeID))
            }

        case "recipe-edit":
            if let id = url.queryItems?["id"], let recipeID = Int(id) {
                // Navigate to recipe, then to edit
                path.append(Destination.recipe(id: recipeID))
                path.append(Destination.recipeEdit(id: recipeID))
            }

        case "reset":
            path = NavigationPath() // Pop to root

        default:
            print("⚠️ Unknown debug URL: \(url)")
        }
    }
    #endif
}

struct ContentView: View {
    @StateObject private var router = DebugRouter()

    var body: some View {
        NavigationStack(path: $router.path) {
            HomeView()
                .navigationDestination(for: Destination.self) { destination in
                    destinationView(for: destination)
                }
        }
        #if DEBUG
        .onOpenURL { url in
            router.handleDebugURL(url)
        }
        #endif
    }

    @ViewBuilder
    private func destinationView(for destination: Destination) -> some View {
        switch destination {
        case .settings:
            SettingsView()
        case .recipe(let id):
            RecipeDetailView(recipeID: id)
        case .recipeEdit(let id):
            RecipeEditView(recipeID: id)
        }
    }
}

enum Destination: Hashable {
    case settings
    case recipe(id: Int)
    case recipeEdit(id: Int)
}

用法:

# Navigate to settings
xcrun simctl openurl booted "debug://settings"

# Navigate to recipe #42
xcrun simctl openurl booted "debug://recipe?id=42"

# Navigate to recipe #42 edit screen
xcrun simctl openurl booted "debug://recipe-edit?id=42"

# Pop to root
xcrun simctl openurl booted "debug://reset"

模式 3:状态配置链接

既能导航又能配置状态的调试链接。

#if DEBUG
extension DebugRouter {
    func handleDebugURL(_ url: URL) {
        guard url.scheme == "debug" else { return }

        switch url.host {
        case "login":
            // Show login screen
            path.append(Destination.login)

        case "login-error":
            // Show login screen WITH error state
            path.append(Destination.login)
            // Trigger error state
            NotificationCenter.default.post(
                name: .showLoginError,
                object: "Invalid credentials"
            )

        case "recipe-empty":
            // Show recipe list in empty state
            UserDefaults.standard.set(true, forKey: "debug_emptyRecipeList")
            path.append(Destination.recipes)

        case "recipe-error":
            // Show recipe list with network error
            UserDefaults.standard.set(true, forKey: "debug_networkError")
            path.append(Destination.recipes)

        default:
            print("⚠️ Unknown debug URL: \(url)")
        }
    }
}
#endif

用法:

# Test login error state
xcrun simctl openurl booted "debug://login-error"

# Test empty recipe list
xcrun simctl openurl booted "debug://recipe-empty"

# Test network error handling
xcrun simctl openurl booted "debug://recipe-error"

模式 4:Info.plist 配置(仅 DEBUG)

仅在调试构建中注册调试 URL scheme。

步骤 1:将 scheme 添加到 Info.plist

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>debug</string>
        </array>
        <key>CFBundleURLName</key>
        <string>com.example.debug</string>
    </dict>
</array>

步骤 2:从发布构建中移除

在目标的 Build Phases 中添加 Run Script 阶段(在 “Copy Bundle Resources” 之前运行):

# Strip debug URL scheme from Release builds
if [ "${CONFIGURATION}" = "Release" ]; then
    echo "Removing debug URL scheme from Info.plist"

    /usr/libexec/PlistBuddy -c "Delete :CFBundleURLTypes:0" "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}" 2>/dev/null || true
fi

替代方案:在 Build Settings 中为 Debug 和 Release 配置使用不同的 Info.plist 文件。


与模拟器测试集成

使用 /axiom:screenshot 命令
# 1. Navigate to screen
xcrun simctl openurl booted "debug://settings"

# 2. Wait for navigation
sleep 1

# 3. Capture screenshot
/axiom:screenshot
使用 simulator-tester Agent

只需告诉该 Agent:

  • “导航到 Settings 并截屏”
  • “打开食谱编辑器并检查布局”
  • “进入错误状态并展示效果”

该 Agent 会使用你的调试深度链接进行导航。


必须完成的前置步骤

添加调试深度链接之前,务必先完成以下步骤:

步骤 1:明确导航需求

列出测试时需要访问的所有屏幕:

- Settings screen
- Profile screen (with specific user ID)
- Recipe detail (with specific recipe ID)
- Error states (login error, network error, etc.)
- Empty states (no recipes, no favorites)
步骤 2:选择 URL scheme 模式
debug://screen-name              # Simple screen navigation
debug://screen-name?param=value  # Navigation with parameters
debug://state-name               # State configuration
步骤 3:添加 URL 处理程序

使用 #if DEBUG 确保代码在发布构建中会被移除。

步骤 4:测试深度链接
# Boot simulator
xcrun simctl boot "iPhone 16 Pro"

# Launch app
xcrun simctl launch booted com.example.YourApp

# Test each deep link
xcrun simctl openurl booted "debug://settings"
xcrun simctl openurl booted "debug://profile?id=123"

常见错误

❌ 错误——在 URL 处理程序中硬编码导航
#if DEBUG
func handleDebugURL(_ url: URL) {
    if url.host == "settings" {
        // ❌ WRONG — Creates tight coupling
        self.showingSettings = true
    }
}
#endif

问题:URL 处理程序现在负责导航逻辑,重复实现了协调器/路由器的模式。

✅ 正确——使用现有导航系统:

#if DEBUG
func handleDebugURL(_ url: URL) {
    if url.host == "settings" {
        // Use existing NavigationPath
        path.append(Destination.settings)
    }
}
#endif

❌ 错误——在生产环境中保留调试代码
// ❌ WRONG — No #if DEBUG
func handleDebugURL(_ url: URL) {
    // This ships to users!
}

问题:调试端点暴露在生产环境中,存在安全风险。

✅ 正确——使用 #if DEBUG 包裹:

#if DEBUG
func handleDebugURL(_ url: URL) {
    // Stripped from release builds
}
#endif

❌ 错误——使用未经验证的查询参数
#if DEBUG
case "profile":
    let userID = Int(url.queryItems?["id"] ?? "0")! // ❌ Force unwrap
    path.append(Destination.profile(id: userID))
#endif

问题:如果缺少 id 或其值无效,就会崩溃。

✅ 正确——验证参数:

#if DEBUG
case "profile":
    guard let idString = url.queryItems?["id"],
          let userID = Int(idString) else {
        print("⚠️ Invalid profile ID")
        return
    }
    path.append(Destination.profile(id: userID))
#endif

测试清单

在自动化工作流中使用调试深层链接之前:

  • URL 处理程序用 #if DEBUG 包裹
  • 已在模拟器中手动测试所有深层链接
  • 已验证参数(不要强制解包)
  • 深层链接与现有导航集成(不要重复实现逻辑)
  • 已从 Release 构建中移除 URL scheme(通过脚本或单独的 Info.plist)
  • 已在 README 或注释中记录,供其他开发者参考
  • 可与 /axiom:screenshot 命令配合使用
  • 可与 simulator-tester Agent 配合使用

真实案例

场景:你正在调试菜谱应用编辑器屏幕中的布局问题。

之前(手动测试):

  1. 构建应用 → 30 秒
  2. 启动模拟器
  3. 点击“菜谱” → 等待加载
  4. 滚动到菜谱 #42
  5. 点击以打开详情
  6. 点击“编辑”
  7. 检查布局是否已修复
  8. 进行更改,重新构建 → 从第 1 步重复 总计:每次迭代需 2-3 分钟

之后(使用调试深层链接):

  1. 构建应用 → 30 秒
  2. 运行:xcrun simctl openurl booted "debug://recipe-edit?id=42"
  3. 运行:/axiom:screenshot
  4. Claude 分析截图并确认布局已修复
  5. 如有需要,进行更改并重新构建 → 从第 2 步重复 总计:每次迭代需 45 秒

节省时间:通过视觉验证,迭代速度提升 60-75%


与现有导航集成

对于使用 NavigationStack 的应用

添加调试 URL 处理程序,将内容追加到现有 NavigationPath:

router.path.append(Destination.fromDebugURL(url))
对于使用 Coordinator 模式的应用

在调试 URL 处理程序中触发 Coordinator 方法:

coordinator.navigate(to: .fromDebugURL(url))
对于使用自定义路由的应用

与路由器的导航 API 集成:

AppRouter.shared.push(Screen.fromDebugURL(url))

核心原则:调试深层链接应使用现有导航,而不是取而代之。


高级模式

模式 5:参数化状态设置
#if DEBUG
case "test-scenario":
    // Parse complex test scenario from URL
    // Example: debug://test-scenario?user=premium&recipes=empty&network=slow

    if let userType = url.queryItems?["user"] {
        configureUser(type: userType) // "premium", "free", "trial"
    }

    if let recipesState = url.queryItems?["recipes"] {
        configureRecipes(state: recipesState) // "empty", "full", "error"
    }

    if let networkState = url.queryItems?["network"] {
        configureNetwork(state: networkState) // "fast", "slow", "offline"
    }

    // Now navigate
    path.append(Destination.recipes)
#endif

用法:

# Test premium user with empty recipe list
xcrun simctl openurl booted "debug://test-scenario?user=premium&recipes=empty"

# Test slow network with error handling
xcrun simctl openurl booted "debug://test-scenario?network=slow&recipes=error"

模式 6:截图自动化辅助工具

创建单个 URL,以设置状态并进行捕获:

#if DEBUG
case "screenshot":
    // Parse screen and configuration
    guard let screen = url.queryItems?["screen"] else { return }

    // Configure state
    if let state = url.queryItems?["state"] {
        applyState(state)
    }

    // Navigate
    navigate(to: screen)

    // Post notification for external capture
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        NotificationCenter.default.post(
            name: .readyForScreenshot,
            object: screen
        )
    }
#endif

用法:

# Navigate to login screen with error state, wait, then screenshot
xcrun simctl openurl booted "debug://screenshot?screen=login&state=error"
sleep 2
xcrun simctl io booted screenshot login-error.png

相关技能

  • axiom-swiftui-nav — 生产环境中的深层链接和 NavigationStack 模式
  • simulator-tester — 使用调试深层链接进行模拟器自动化测试
  • axiom-xcode-debugging — 环境优先的调试工作流

总结

调试深层链接可实现:

  • 闭环调试,并进行视觉验证
  • 视觉修复的迭代速度提升 60-75%
  • 自动化测试,无需手动导航
  • 为任何应用状态自动截图

请记住:

  1. 用 #if DEBUG 包裹所有调试代码
  2. 从 Release 构建中移除 URL scheme
  3. 与现有导航集成,不要重复实现
  4. 验证所有参数(不要强制解包)
  5. 为团队成员提供文档说明

评论

支持 Markdown