Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add artimath/i-know-kung-fu --skill "ui-testing"
Install specific skill from multi-skill repository
# Description
This skill should be used when the user asks to "write UI tests", "add XCUITest", "test accessibility", "automate macOS UI", or needs guidance on XCTest patterns, accessibility identifiers, keyboard shortcut testing, or menu item automation for macOS SwiftUI apps.
# SKILL.md
name: ui-testing
description: This skill should be used when the user asks to "write UI tests", "add XCUITest", "test accessibility", "automate macOS UI", or needs guidance on XCTest patterns, accessibility identifiers, keyboard shortcut testing, or menu item automation for macOS SwiftUI apps.
version: 1.0.0
UI Testing Skill for macOS SwiftUI Apps
Overview
Comprehensive patterns for testing macOS SwiftUI apps using XCTest and XCUITest.
Test Architecture
Test Pyramid
- Unit tests (Swift Testing
@Test): Model logic, algorithms, pure functions - Integration tests (Swift Testing): Component interactions, data flow
- UI tests (XCTest
XCTestCase): User interactions, menu items, keyboard shortcuts
Framework Usage
// Unit/Integration - Use Swift Testing
import Testing
@Test func confidencePropagation() {
// Fast, isolated tests
}
// UI Automation - Use XCTest (required for XCUIApplication)
import XCTest
class MyAppUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
}
Accessibility Identifiers
CRITICAL: Every testable element needs an accessibility identifier.
// SwiftUI View
Canvas { ... }
.accessibilityIdentifier("graphCanvas")
Button("Save") { ... }
.accessibilityIdentifier("saveButton")
// Node in Canvas - use node ID
.accessibilityIdentifier("node-\(node.id)")
Naming Convention
- Canvas:
"graphCanvas" - Nodes:
"node-{id}" - Edges:
"edge-{id}" - Inspectors:
"inspector-document","inspector-element", etc. - Toolbar buttons:
"toolbar-{action}"
Testing Patterns
1. Menu Item Tests
func testNewEntityMenuItem() throws {
let menuBar = app.menuBars
menuBar.menuBarItems["Entity"].click()
let newEntity = menuBar.menuItems["New Entity"]
XCTAssertTrue(newEntity.waitForExistence(timeout: 2))
// Actually click it and verify behavior
newEntity.click()
// Verify a node was created (check inspector or canvas)
let elementInspector = app.groups["inspector-element"]
XCTAssertTrue(elementInspector.waitForExistence(timeout: 2))
}
2. Keyboard Shortcut Tests
func testNewEntityShortcut() throws {
// Create new document first
app.typeKey("n", modifierFlags: .command)
sleep(1)
// Get node count before
let canvas = app.otherElements["graphCanvas"]
// Press Cmd+E for new entity
app.typeKey("e", modifierFlags: .command)
sleep(1)
// Verify entity was created via inspector or element count
let elementInspector = app.groups["inspector-element"]
XCTAssertTrue(elementInspector.exists, "Element inspector should show selected entity")
}
3. Canvas Drag Tests (Coordinate-based)
func testDragNodeOnCanvas() throws {
let canvas = app.otherElements["graphCanvas"]
XCTAssertTrue(canvas.waitForExistence(timeout: 5))
// Create a node first
app.typeKey("e", modifierFlags: .command)
sleep(1)
// Drag from center to offset position
let startPoint = canvas.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let endPoint = startPoint.withOffset(CGVector(dx: 100, dy: 50))
startPoint.press(forDuration: 0.1, thenDragTo: endPoint)
}
4. Edge Creation by Drag
func testCreateEdgeByDrag() throws {
let canvas = app.otherElements["graphCanvas"]
// Create two nodes
app.typeKey("e", modifierFlags: .command)
sleep(1)
app.typeKey("e", modifierFlags: .command)
sleep(1)
// Option+drag from first node to second (edge creation)
let node1 = app.otherElements["node-1"] // needs accessibilityIdentifier
let node2 = app.otherElements["node-2"]
let start = node1.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = node2.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
// Hold Option while dragging
start.press(forDuration: 0.1, thenDragTo: end, withVelocity: .default, thenHoldForDuration: 0.1)
}
5. Inspector Panel Tests
func testDocumentInspectorFields() throws {
// Cmd+1 to show Document Inspector
app.typeKey("1", modifierFlags: .command)
sleep(1)
let titleField = app.textFields["document-title"]
XCTAssertTrue(titleField.waitForExistence(timeout: 2))
// Edit title
titleField.click()
titleField.typeText("My Document")
// Verify it took
XCTAssertEqual(titleField.value as? String, "My Document")
}
6. Undo/Redo Tests
func testUndoRedoEntity() throws {
// Create entity
app.typeKey("e", modifierFlags: .command)
sleep(1)
// Verify entity exists
let inspector = app.groups["inspector-element"]
XCTAssertTrue(inspector.exists)
// Undo
app.typeKey("z", modifierFlags: .command)
sleep(1)
// Entity should be gone
XCTAssertFalse(inspector.exists, "Entity should be removed after undo")
// Redo
app.typeKey("z", modifierFlags: [.command, .shift])
sleep(1)
// Entity should be back
XCTAssertTrue(inspector.waitForExistence(timeout: 2), "Entity should return after redo")
}
7. Copy/Paste Tests
func testCopyPasteEntity() throws {
// Create and select entity
app.typeKey("e", modifierFlags: .command)
sleep(1)
// Copy
app.typeKey("c", modifierFlags: .command)
sleep(1)
// Paste
app.typeKey("v", modifierFlags: .command)
sleep(1)
// Should now have 2 nodes
// Verify via graph state or UI element count
}
Helper Extensions
extension XCUIElement {
/// Wait for element and tap
func waitAndTap(timeout: TimeInterval = 5) {
XCTAssertTrue(waitForExistence(timeout: timeout))
tap()
}
/// Clear text field
func clearAndType(_ text: String) {
guard let stringValue = value as? String else { return }
tap()
let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
typeText(deleteString)
typeText(text)
}
}
Running UI Tests
# Generate Xcode project
xcodegen generate
# Run all UI tests
xcodebuild test -scheme MyApp -destination 'platform=macOS' -only-testing MyAppUITests
# Run specific test
xcodebuild test -scheme MyApp -destination 'platform=macOS' -only-testing MyAppUITests/MyAppUITests/testNewEntityMenuItem
# Or use script
./scripts/run-ui-tests.sh --test testNewEntityMenuItem
Checklist for New Features
When implementing a new feature, ensure:
1. [ ] Accessibility identifier added to all interactive elements
2. [ ] Unit test for model/logic (Swift Testing)
3. [ ] UI test verifying menu item exists
4. [ ] UI test verifying keyboard shortcut works
5. [ ] UI test verifying the action produces expected result
6. [ ] UI test for undo/redo if applicable
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.