test(ui): add XCUITest console smoke per CLAUDE.md §9
- MetrikaUITests target (xcodegen scheme test action) - launch-environment hook (DEBUG only): sandboxed app materializes the test CSV in its own container, since the runner cannot stage files across the sandbox boundary reliably - queries predicate on AX 'value' (SwiftUI Text exposes value, not label) - smoke: launch, auto-load dataset, type gen + reg robust, assert regression table and sidebar variable Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 5 changed files with +335 and −0
modified
Metrika.xcodeproj/project.pbxproj
+119 −0
@@ -12,18 +12,31 @@ | ||
| 12 | 12 | 7E936A23CCC90823D6B08BCC /* SessionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0453FB0F256E853D2C2F481C /* SessionModel.swift */; }; |
| 13 | 13 | 8B0CDB213D64E3EE87593808 /* ConsoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159695CF89D6BB25DF883F07 /* ConsoleView.swift */; }; |
| 14 | 14 | 9BE65709781719FAE0CA740A /* MetrikaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C5393DC050C555E4FB69FC0 /* MetrikaApp.swift */; }; |
| 15 | + CB602D8A4EDDF5D0FBB0CFB7 /* ConsoleSmokeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7ECE59F86249B3A25B17A4 /* ConsoleSmokeTests.swift */; }; | |
| 15 | 16 | DDCC2DC487AF9FAC7D6B8E3E /* PlotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD551AA707CD1853C3D6D21 /* PlotView.swift */; }; |
| 16 | 17 | /* End PBXBuildFile section */ |
| 17 | 18 | |
| 19 | +/* Begin PBXContainerItemProxy section */ | |
| 20 | + 13CEFED09749BEF836D4CD43 /* PBXContainerItemProxy */ = { | |
| 21 | + isa = PBXContainerItemProxy; | |
| 22 | + containerPortal = 8571D0059EB0A97DE47F96F7 /* Project object */; | |
| 23 | + proxyType = 1; | |
| 24 | + remoteGlobalIDString = 5E03B33ABE9DE3162C326F87; | |
| 25 | + remoteInfo = Metrika; | |
| 26 | + }; | |
| 27 | +/* End PBXContainerItemProxy section */ | |
| 28 | + | |
| 18 | 29 | /* Begin PBXFileReference section */ |
| 19 | 30 | 0404FDEAF8A57414EFB3C649 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; |
| 20 | 31 | 0453FB0F256E853D2C2F481C /* SessionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionModel.swift; sourceTree = "<group>"; }; |
| 21 | 32 | 159695CF89D6BB25DF883F07 /* ConsoleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleView.swift; sourceTree = "<group>"; }; |
| 22 | 33 | 24D84DAB354D702D8575D587 /* MetrikaKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = MetrikaKit; path = MetrikaKit; sourceTree = SOURCE_ROOT; }; |
| 34 | + 2B7CDF1616657B2E1118696A /* MetrikaUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MetrikaUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; | |
| 23 | 35 | 4AD551AA707CD1853C3D6D21 /* PlotView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlotView.swift; sourceTree = "<group>"; }; |
| 24 | 36 | 5A0213CC9FE05A339DEEFA28 /* Metrika.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metrika.app; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 25 | 37 | 6E94C5817E96C83B888FB579 /* Metrika.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Metrika.entitlements; sourceTree = "<group>"; }; |
| 26 | 38 | 9C5393DC050C555E4FB69FC0 /* MetrikaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetrikaApp.swift; sourceTree = "<group>"; }; |
| 39 | + AB7ECE59F86249B3A25B17A4 /* ConsoleSmokeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleSmokeTests.swift; sourceTree = "<group>"; }; | |
| 27 | 40 | /* End PBXFileReference section */ |
| 28 | 41 | |
| 29 | 42 | /* Begin PBXFrameworksBuildPhase section */ |
@@ -42,10 +55,19 @@ | ||
| 42 | 55 | isa = PBXGroup; |
| 43 | 56 | children = ( |
| 44 | 57 | 5A0213CC9FE05A339DEEFA28 /* Metrika.app */, |
| 58 | + 2B7CDF1616657B2E1118696A /* MetrikaUITests.xctest */, | |
| 45 | 59 | ); |
| 46 | 60 | name = Products; |
| 47 | 61 | sourceTree = "<group>"; |
| 48 | 62 | }; |
| 63 | + 16F58F4F30568D46FED9725C /* Tests */ = { | |
| 64 | + isa = PBXGroup; | |
| 65 | + children = ( | |
| 66 | + 8DB762C8E40DC80A320B948A /* UITests */, | |
| 67 | + ); | |
| 68 | + path = Tests; | |
| 69 | + sourceTree = "<group>"; | |
| 70 | + }; | |
| 49 | 71 | 58DB3AB5F820BD1E6C82E1CB /* Sources */ = { |
| 50 | 72 | isa = PBXGroup; |
| 51 | 73 | children = ( |
@@ -65,6 +87,14 @@ | ||
| 65 | 87 | name = Packages; |
| 66 | 88 | sourceTree = "<group>"; |
| 67 | 89 | }; |
| 90 | + 8DB762C8E40DC80A320B948A /* UITests */ = { | |
| 91 | + isa = PBXGroup; | |
| 92 | + children = ( | |
| 93 | + AB7ECE59F86249B3A25B17A4 /* ConsoleSmokeTests.swift */, | |
| 94 | + ); | |
| 95 | + path = UITests; | |
| 96 | + sourceTree = "<group>"; | |
| 97 | + }; | |
| 68 | 98 | A905F8EA05CC983967935417 /* Metrika */ = { |
| 69 | 99 | isa = PBXGroup; |
| 70 | 100 | children = ( |
@@ -80,6 +110,7 @@ | ||
| 80 | 110 | children = ( |
| 81 | 111 | A905F8EA05CC983967935417 /* Metrika */, |
| 82 | 112 | 63F77F28B4EF42E0407BEC4F /* Packages */, |
| 113 | + 16F58F4F30568D46FED9725C /* Tests */, | |
| 83 | 114 | 133558B8D49331BB5120F8F8 /* Products */, |
| 84 | 115 | ); |
| 85 | 116 | sourceTree = "<group>"; |
@@ -87,6 +118,24 @@ | ||
| 87 | 118 | /* End PBXGroup section */ |
| 88 | 119 | |
| 89 | 120 | /* Begin PBXNativeTarget section */ |
| 121 | + 50B025D4EDA8EDC95554C858 /* MetrikaUITests */ = { | |
| 122 | + isa = PBXNativeTarget; | |
| 123 | + buildConfigurationList = 6538F0F675B5443C747BB776 /* Build configuration list for PBXNativeTarget "MetrikaUITests" */; | |
| 124 | + buildPhases = ( | |
| 125 | + 0CBD8711F5ED186EFD33CFF6 /* Sources */, | |
| 126 | + ); | |
| 127 | + buildRules = ( | |
| 128 | + ); | |
| 129 | + dependencies = ( | |
| 130 | + 0F2156F03062FA09F4D1A975 /* PBXTargetDependency */, | |
| 131 | + ); | |
| 132 | + name = MetrikaUITests; | |
| 133 | + packageProductDependencies = ( | |
| 134 | + ); | |
| 135 | + productName = MetrikaUITests; | |
| 136 | + productReference = 2B7CDF1616657B2E1118696A /* MetrikaUITests.xctest */; | |
| 137 | + productType = "com.apple.product-type.bundle.ui-testing"; | |
| 138 | + }; | |
| 90 | 139 | 5E03B33ABE9DE3162C326F87 /* Metrika */ = { |
| 91 | 140 | isa = PBXNativeTarget; |
| 92 | 141 | buildConfigurationList = A087F0DEACCF786E9B7CC9A5 /* Build configuration list for PBXNativeTarget "Metrika" */; |
@@ -116,6 +165,10 @@ | ||
| 116 | 165 | BuildIndependentTargetsInParallel = YES; |
| 117 | 166 | LastUpgradeCheck = 1430; |
| 118 | 167 | TargetAttributes = { |
| 168 | + 50B025D4EDA8EDC95554C858 = { | |
| 169 | + ProvisioningStyle = Automatic; | |
| 170 | + TestTargetID = 5E03B33ABE9DE3162C326F87; | |
| 171 | + }; | |
| 119 | 172 | 5E03B33ABE9DE3162C326F87 = { |
| 120 | 173 | ProvisioningStyle = Automatic; |
| 121 | 174 | }; |
@@ -139,6 +192,7 @@ | ||
| 139 | 192 | projectRoot = ""; |
| 140 | 193 | targets = ( |
| 141 | 194 | 5E03B33ABE9DE3162C326F87 /* Metrika */, |
| 195 | + 50B025D4EDA8EDC95554C858 /* MetrikaUITests */, | |
| 142 | 196 | ); |
| 143 | 197 | }; |
| 144 | 198 | /* End PBXProject section */ |
@@ -155,6 +209,14 @@ | ||
| 155 | 209 | /* End PBXResourcesBuildPhase section */ |
| 156 | 210 | |
| 157 | 211 | /* Begin PBXSourcesBuildPhase section */ |
| 212 | + 0CBD8711F5ED186EFD33CFF6 /* Sources */ = { | |
| 213 | + isa = PBXSourcesBuildPhase; | |
| 214 | + buildActionMask = 2147483647; | |
| 215 | + files = ( | |
| 216 | + CB602D8A4EDDF5D0FBB0CFB7 /* ConsoleSmokeTests.swift in Sources */, | |
| 217 | + ); | |
| 218 | + runOnlyForDeploymentPostprocessing = 0; | |
| 219 | + }; | |
| 158 | 220 | C8918D5868AE76549F9247E4 /* Sources */ = { |
| 159 | 221 | isa = PBXSourcesBuildPhase; |
| 160 | 222 | buildActionMask = 2147483647; |
@@ -168,6 +230,14 @@ | ||
| 168 | 230 | }; |
| 169 | 231 | /* End PBXSourcesBuildPhase section */ |
| 170 | 232 | |
| 233 | +/* Begin PBXTargetDependency section */ | |
| 234 | + 0F2156F03062FA09F4D1A975 /* PBXTargetDependency */ = { | |
| 235 | + isa = PBXTargetDependency; | |
| 236 | + target = 5E03B33ABE9DE3162C326F87 /* Metrika */; | |
| 237 | + targetProxy = 13CEFED09749BEF836D4CD43 /* PBXContainerItemProxy */; | |
| 238 | + }; | |
| 239 | +/* End PBXTargetDependency section */ | |
| 240 | + | |
| 171 | 241 | /* Begin XCBuildConfiguration section */ |
| 172 | 242 | 3CB58B068179C9DB2CD1CAC1 /* Release */ = { |
| 173 | 243 | isa = XCBuildConfiguration; |
@@ -225,6 +295,26 @@ | ||
| 225 | 295 | }; |
| 226 | 296 | name = Release; |
| 227 | 297 | }; |
| 298 | + 441F73FC566D118C4502BB84 /* Release */ = { | |
| 299 | + isa = XCBuildConfiguration; | |
| 300 | + buildSettings = { | |
| 301 | + ARCHS = arm64; | |
| 302 | + BUNDLE_LOADER = "$(TEST_HOST)"; | |
| 303 | + CODE_SIGN_STYLE = Automatic; | |
| 304 | + COMBINE_HIDPI_IMAGES = YES; | |
| 305 | + GENERATE_INFOPLIST_FILE = YES; | |
| 306 | + LD_RUNPATH_SEARCH_PATHS = ( | |
| 307 | + "$(inherited)", | |
| 308 | + "@executable_path/Frameworks", | |
| 309 | + "@loader_path/Frameworks", | |
| 310 | + ); | |
| 311 | + PRODUCT_BUNDLE_IDENTIFIER = ai.spboucher.metrika.uitests; | |
| 312 | + SDKROOT = macosx; | |
| 313 | + SWIFT_VERSION = 6.0; | |
| 314 | + TEST_TARGET_NAME = Metrika; | |
| 315 | + }; | |
| 316 | + name = Release; | |
| 317 | + }; | |
| 228 | 318 | 4D5E4B6EED8197B03EAD7E51 /* Debug */ = { |
| 229 | 319 | isa = XCBuildConfiguration; |
| 230 | 320 | buildSettings = { |
@@ -252,6 +342,26 @@ | ||
| 252 | 342 | }; |
| 253 | 343 | name = Debug; |
| 254 | 344 | }; |
| 345 | + A29A62A39C08413EA3AEAA14 /* Debug */ = { | |
| 346 | + isa = XCBuildConfiguration; | |
| 347 | + buildSettings = { | |
| 348 | + ARCHS = arm64; | |
| 349 | + BUNDLE_LOADER = "$(TEST_HOST)"; | |
| 350 | + CODE_SIGN_STYLE = Automatic; | |
| 351 | + COMBINE_HIDPI_IMAGES = YES; | |
| 352 | + GENERATE_INFOPLIST_FILE = YES; | |
| 353 | + LD_RUNPATH_SEARCH_PATHS = ( | |
| 354 | + "$(inherited)", | |
| 355 | + "@executable_path/Frameworks", | |
| 356 | + "@loader_path/Frameworks", | |
| 357 | + ); | |
| 358 | + PRODUCT_BUNDLE_IDENTIFIER = ai.spboucher.metrika.uitests; | |
| 359 | + SDKROOT = macosx; | |
| 360 | + SWIFT_VERSION = 6.0; | |
| 361 | + TEST_TARGET_NAME = Metrika; | |
| 362 | + }; | |
| 363 | + name = Debug; | |
| 364 | + }; | |
| 255 | 365 | C241F8724816C2E6FEFA98F2 /* Release */ = { |
| 256 | 366 | isa = XCBuildConfiguration; |
| 257 | 367 | buildSettings = { |
@@ -354,6 +464,15 @@ | ||
| 354 | 464 | defaultConfigurationIsVisible = 0; |
| 355 | 465 | defaultConfigurationName = Debug; |
| 356 | 466 | }; |
| 467 | + 6538F0F675B5443C747BB776 /* Build configuration list for PBXNativeTarget "MetrikaUITests" */ = { | |
| 468 | + isa = XCConfigurationList; | |
| 469 | + buildConfigurations = ( | |
| 470 | + A29A62A39C08413EA3AEAA14 /* Debug */, | |
| 471 | + 441F73FC566D118C4502BB84 /* Release */, | |
| 472 | + ); | |
| 473 | + defaultConfigurationIsVisible = 0; | |
| 474 | + defaultConfigurationName = Debug; | |
| 475 | + }; | |
| 357 | 476 | A087F0DEACCF786E9B7CC9A5 /* Build configuration list for PBXNativeTarget "Metrika" */ = { |
| 358 | 477 | isa = XCConfigurationList; |
| 359 | 478 | buildConfigurations = ( |
added
Metrika.xcodeproj/xcshareddata/xcschemes/Metrika.xcscheme
+104 −0
@@ -0,0 +1,104 @@ | ||
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<Scheme | |
| 3 | + LastUpgradeVersion = "1430" | |
| 4 | + version = "1.7"> | |
| 5 | + <BuildAction | |
| 6 | + parallelizeBuildables = "YES" | |
| 7 | + buildImplicitDependencies = "YES" | |
| 8 | + runPostActionsOnFailure = "NO"> | |
| 9 | + <BuildActionEntries> | |
| 10 | + <BuildActionEntry | |
| 11 | + buildForTesting = "YES" | |
| 12 | + buildForRunning = "YES" | |
| 13 | + buildForProfiling = "YES" | |
| 14 | + buildForArchiving = "YES" | |
| 15 | + buildForAnalyzing = "YES"> | |
| 16 | + <BuildableReference | |
| 17 | + BuildableIdentifier = "primary" | |
| 18 | + BlueprintIdentifier = "5E03B33ABE9DE3162C326F87" | |
| 19 | + BuildableName = "Metrika.app" | |
| 20 | + BlueprintName = "Metrika" | |
| 21 | + ReferencedContainer = "container:Metrika.xcodeproj"> | |
| 22 | + </BuildableReference> | |
| 23 | + </BuildActionEntry> | |
| 24 | + </BuildActionEntries> | |
| 25 | + </BuildAction> | |
| 26 | + <TestAction | |
| 27 | + buildConfiguration = "Debug" | |
| 28 | + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | |
| 29 | + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | |
| 30 | + shouldUseLaunchSchemeArgsEnv = "YES" | |
| 31 | + onlyGenerateCoverageForSpecifiedTargets = "NO"> | |
| 32 | + <MacroExpansion> | |
| 33 | + <BuildableReference | |
| 34 | + BuildableIdentifier = "primary" | |
| 35 | + BlueprintIdentifier = "5E03B33ABE9DE3162C326F87" | |
| 36 | + BuildableName = "Metrika.app" | |
| 37 | + BlueprintName = "Metrika" | |
| 38 | + ReferencedContainer = "container:Metrika.xcodeproj"> | |
| 39 | + </BuildableReference> | |
| 40 | + </MacroExpansion> | |
| 41 | + <Testables> | |
| 42 | + <TestableReference | |
| 43 | + skipped = "NO" | |
| 44 | + parallelizable = "NO"> | |
| 45 | + <BuildableReference | |
| 46 | + BuildableIdentifier = "primary" | |
| 47 | + BlueprintIdentifier = "50B025D4EDA8EDC95554C858" | |
| 48 | + BuildableName = "MetrikaUITests.xctest" | |
| 49 | + BlueprintName = "MetrikaUITests" | |
| 50 | + ReferencedContainer = "container:Metrika.xcodeproj"> | |
| 51 | + </BuildableReference> | |
| 52 | + </TestableReference> | |
| 53 | + </Testables> | |
| 54 | + <CommandLineArguments> | |
| 55 | + </CommandLineArguments> | |
| 56 | + </TestAction> | |
| 57 | + <LaunchAction | |
| 58 | + buildConfiguration = "Debug" | |
| 59 | + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | |
| 60 | + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | |
| 61 | + launchStyle = "0" | |
| 62 | + useCustomWorkingDirectory = "NO" | |
| 63 | + ignoresPersistentStateOnLaunch = "NO" | |
| 64 | + debugDocumentVersioning = "YES" | |
| 65 | + debugServiceExtension = "internal" | |
| 66 | + allowLocationSimulation = "YES"> | |
| 67 | + <BuildableProductRunnable | |
| 68 | + runnableDebuggingMode = "0"> | |
| 69 | + <BuildableReference | |
| 70 | + BuildableIdentifier = "primary" | |
| 71 | + BlueprintIdentifier = "5E03B33ABE9DE3162C326F87" | |
| 72 | + BuildableName = "Metrika.app" | |
| 73 | + BlueprintName = "Metrika" | |
| 74 | + ReferencedContainer = "container:Metrika.xcodeproj"> | |
| 75 | + </BuildableReference> | |
| 76 | + </BuildableProductRunnable> | |
| 77 | + <CommandLineArguments> | |
| 78 | + </CommandLineArguments> | |
| 79 | + </LaunchAction> | |
| 80 | + <ProfileAction | |
| 81 | + buildConfiguration = "Release" | |
| 82 | + shouldUseLaunchSchemeArgsEnv = "YES" | |
| 83 | + savedToolIdentifier = "" | |
| 84 | + useCustomWorkingDirectory = "NO" | |
| 85 | + debugDocumentVersioning = "YES"> | |
| 86 | + <BuildableProductRunnable | |
| 87 | + runnableDebuggingMode = "0"> | |
| 88 | + <BuildableReference | |
| 89 | + BuildableIdentifier = "primary" | |
| 90 | + BlueprintIdentifier = "5E03B33ABE9DE3162C326F87" | |
| 91 | + BuildableName = "Metrika.app" | |
| 92 | + BlueprintName = "Metrika" | |
| 93 | + ReferencedContainer = "container:Metrika.xcodeproj"> | |
| 94 | + </BuildableReference> | |
| 95 | + </BuildableProductRunnable> | |
| 96 | + </ProfileAction> | |
| 97 | + <AnalyzeAction | |
| 98 | + buildConfiguration = "Debug"> | |
| 99 | + </AnalyzeAction> | |
| 100 | + <ArchiveAction | |
| 101 | + buildConfiguration = "Release" | |
| 102 | + revealArchiveInOrganizer = "YES"> | |
| 103 | + </ArchiveAction> | |
| 104 | +</Scheme> | |
modified
Metrika/Sources/SessionModel.swift
+11 −0
@@ -51,6 +51,17 @@ final class SessionModel { | ||
| 51 | 51 | isError: true |
| 52 | 52 | )) |
| 53 | 53 | } |
| 54 | + #if DEBUG | |
| 55 | + // XCUITest hook: the sandboxed app cannot read files staged by the | |
| 56 | + // test runner, so the test passes CSV content via the environment | |
| 57 | + // and the app materializes it inside its own container. | |
| 58 | + if let csv = ProcessInfo.processInfo.environment["METRIKA_UITEST_CSV"] { | |
| 59 | + let url = FileManager.default.temporaryDirectory | |
| 60 | + .appendingPathComponent("uitest_smoke.csv") | |
| 61 | + try? Data(csv.utf8).write(to: url) | |
| 62 | + Task { await run("use \(url.path)") } | |
| 63 | + } | |
| 64 | + #endif | |
| 54 | 65 | } |
| 55 | 66 | |
| 56 | 67 | func run(_ command: String) async { |
added
Tests/UITests/ConsoleSmokeTests.swift
+73 −0
@@ -0,0 +1,73 @@ | ||
| 1 | +// | |
| 2 | +// ConsoleSmokeTests.swift | |
| 3 | +// Metrika | |
| 4 | +// | |
| 5 | +// Author: Simon-Pierre Boucher | |
| 6 | +// Contact: contact@spboucher.ai | |
| 7 | +// Copyright © 2026 Simon-Pierre Boucher. All rights reserved. | |
| 8 | +// | |
| 9 | + | |
| 10 | +import XCTest | |
| 11 | + | |
| 12 | +/// XCUITest smoke (CLAUDE.md §9): launch the app, load a sample dataset, | |
| 13 | +/// run a regression, and assert the output table appears in the console. | |
| 14 | +/// | |
| 15 | +/// The sandboxed app cannot read files staged by the test runner (and its | |
| 16 | +/// container may be recreated between runs), so the dataset travels via | |
| 17 | +/// `METRIKA_UITEST_CSV` in the launch environment; the app writes it into | |
| 18 | +/// its own container and `use`s it on startup. | |
| 19 | +/// | |
| 20 | +/// SwiftUI `Text` exposes content through the accessibility **value** | |
| 21 | +/// attribute, so all queries predicate on `value`, not `label`. | |
| 22 | +final class ConsoleSmokeTests: XCTestCase { | |
| 23 | + | |
| 24 | + private func staticText( | |
| 25 | + containing needle: String, in app: XCUIApplication | |
| 26 | + ) -> XCUIElement { | |
| 27 | + app.staticTexts.matching( | |
| 28 | + NSPredicate(format: "value CONTAINS %@", needle) | |
| 29 | + ).firstMatch | |
| 30 | + } | |
| 31 | + | |
| 32 | + func testLoadDatasetAndRegress() throws { | |
| 33 | + continueAfterFailure = false | |
| 34 | + | |
| 35 | + let app = XCUIApplication() | |
| 36 | + app.launchEnvironment["METRIKA_UITEST_CSV"] = """ | |
| 37 | + revenue,price,region,firm_id | |
| 38 | + 120.5,10.2,1,1 | |
| 39 | + 98.3,12.1,1,1 | |
| 40 | + 143.2,9.8,2,2 | |
| 41 | + 110.0,11.5,2,2 | |
| 42 | + 155.9,8.9,3,3 | |
| 43 | + 101.2,12.8,3,3 | |
| 44 | + 133.4,10.1,1,4 | |
| 45 | + 88.7,13.5,2,4 | |
| 46 | + 149.1,9.2,3,5 | |
| 47 | + 115.6,11.0,1,5 | |
| 48 | + """ | |
| 49 | + app.launch() | |
| 50 | + | |
| 51 | + // Startup hook loads the dataset. | |
| 52 | + let loaded = staticText(containing: "10 observations, 4 variables", in: app) | |
| 53 | + XCTAssertTrue(loaded.waitForExistence(timeout: 10), "dataset did not load") | |
| 54 | + | |
| 55 | + let input = app.textFields.firstMatch | |
| 56 | + XCTAssertTrue(input.waitForExistence(timeout: 5), "command field missing") | |
| 57 | + input.click() | |
| 58 | + input.typeText("gen log_rev = ln(revenue)\n") | |
| 59 | + input.typeText("reg log_rev price, robust\n") | |
| 60 | + | |
| 61 | + // Regression output: robust banner and coefficient table. | |
| 62 | + let banner = staticText(containing: "Linear regression (robust)", in: app) | |
| 63 | + XCTAssertTrue(banner.waitForExistence(timeout: 10), "regression output missing") | |
| 64 | + XCTAssertTrue( | |
| 65 | + staticText(containing: "Coefficient", in: app).exists, | |
| 66 | + "coefficient table missing" | |
| 67 | + ) | |
| 68 | + | |
| 69 | + // Sidebar reflects the generated variable. | |
| 70 | + let sidebar = staticText(containing: "log_rev", in: app) | |
| 71 | + XCTAssertTrue(sidebar.waitForExistence(timeout: 5), "sidebar missing log_rev") | |
| 72 | + } | |
| 73 | +} | |
modified
project.yml
+28 −0
@@ -9,7 +9,35 @@ packages: | ||
| 9 | 9 | MetrikaKit: |
| 10 | 10 | path: MetrikaKit |
| 11 | 11 | |
| 12 | +schemes: | |
| 13 | + Metrika: | |
| 14 | + build: | |
| 15 | + targets: | |
| 16 | + Metrika: all | |
| 17 | + run: | |
| 18 | + config: Debug | |
| 19 | + test: | |
| 20 | + config: Debug | |
| 21 | + targets: | |
| 22 | + - MetrikaUITests | |
| 23 | + | |
| 12 | 24 | targets: |
| 25 | + MetrikaUITests: | |
| 26 | + type: bundle.ui-testing | |
| 27 | + platform: macOS | |
| 28 | + sources: | |
| 29 | + - Tests/UITests | |
| 30 | + dependencies: | |
| 31 | + - target: Metrika | |
| 32 | + settings: | |
| 33 | + base: | |
| 34 | + PRODUCT_BUNDLE_IDENTIFIER: ai.spboucher.metrika.uitests | |
| 35 | + SWIFT_VERSION: "6.0" | |
| 36 | + ARCHS: arm64 | |
| 37 | + GENERATE_INFOPLIST_FILE: YES | |
| 38 | + TEST_TARGET_NAME: Metrika | |
| 39 | + CODE_SIGN_STYLE: Automatic | |
| 40 | + | |
| 13 | 41 | Metrika: |
| 14 | 42 | type: application |
| 15 | 43 | platform: macOS |
| 16 | 44 | |