Tests/UI/ListlessMacScreenshots.swift (5776B)
1import CoreGraphics
2import XCTest
3
4@MainActor
5final class ListlessMacScreenshots: XCTestCase {
6 var app: XCUIApplication!
7
8 override func tearDownWithError() throws {
9 app.terminate()
10 }
11
12
13
14 private func launchApp(_ additionalArgs: [String] = []) {
15 app = XCUIApplication()
16 let appearance = additionalArgs.contains("SCREENSHOT_DARK") ? "SCREENSHOT_DARK" : "SCREENSHOT_LIGHT"
17 app.launchArguments = ["UI_TESTING", appearance] + additionalArgs.filter { $0 != "SCREENSHOT_DARK" }
18 app.launch()
19 }
20
21 var draftTextField: XCUIElement {
22 app.textFields["draft-row-append"]
23 }
24
25 func itemText(_ title: String) -> XCUIElement {
26 app.textFields.matching(
27 NSPredicate(format: "identifier BEGINSWITH 'item-text-' AND value == %@", title)
28 ).firstMatch
29 }
30
31 func createItem(_ title: String) {
32 let textField = draftTextField
33 if !textField.exists {
34 app.typeKey("n", modifierFlags: .command)
35 if !textField.waitForExistence(timeout: 2) {
36 XCTFail("Draft text field should appear after Cmd+N")
37 return
38 }
39 }
40 textField.click()
41 textField.typeText(title)
42 textField.typeKey(.return, modifierFlags: [])
43 }
44
45 func itemCheckbox(at index: Int) -> XCUIElement {
46 app.buttons.matching(identifier: "item-checkbox").element(boundBy: index)
47 }
48
49 func navigateToItem(at index: Int) {
50 app.typeKey(.escape, modifierFlags: [])
51 for _ in 0...index {
52 app.typeKey(.downArrow, modifierFlags: [])
53 }
54 }
55
56 func cmdClickRow(withText title: String) {
57 let textField = itemText(title)
58 XCTAssertTrue(textField.waitForExistence(timeout: 2))
59 let coord = textField.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0.5))
60 .withOffset(CGVector(dx: -40, dy: 0))
61 let point = coord.screenPoint
62
63 let src = CGEventSource(stateID: .combinedSessionState)
64
65 let mouseDown = CGEvent(
66 mouseEventSource: src, mouseType: .leftMouseDown,
67 mouseCursorPosition: point, mouseButton: .left
68 )!
69 mouseDown.flags = .maskCommand
70 mouseDown.post(tap: .cgSessionEventTap)
71 usleep(50_000)
72
73 let mouseUp = CGEvent(
74 mouseEventSource: src, mouseType: .leftMouseUp,
75 mouseCursorPosition: point, mouseButton: .left
76 )!
77 mouseUp.flags = .maskCommand
78 mouseUp.post(tap: .cgSessionEventTap)
79 usleep(200_000)
80 }
81
82 func saveScreenshot(name: String) {
83 let windowList = CGWindowListCopyWindowInfo(
84 [.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID
85 ) as? [[String: Any]] ?? []
86
87 guard let windowID = windowList.first(where: {
88 ($0[kCGWindowOwnerName as String] as? String) == "Listless"
89 && ($0[kCGWindowLayer as String] as? Int) == 0
90 })?[kCGWindowNumber as String] as? Int else {
91 XCTFail("Could not find Listless window")
92 return
93 }
94
95 let tmpFile = NSTemporaryDirectory() + "\(name).png"
96 let process = Process()
97 process.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
98 process.arguments = ["-l\(windowID)", tmpFile]
99 try? process.run()
100 process.waitUntilExit()
101
102 guard let data = try? Data(contentsOf: URL(fileURLWithPath: tmpFile)) else {
103 XCTFail("screencapture failed to write \(tmpFile)")
104 return
105 }
106
107 let attachment = XCTAttachment(data: data, uniformTypeIdentifier: "public.png")
108 attachment.name = name
109 attachment.lifetime = .keepAlways
110 add(attachment)
111
112 try? FileManager.default.removeItem(atPath: tmpFile)
113 }
114
115
116
117
118
119 func testScreenshot01_ItemsWithEditing() throws {
120 launchApp()
121
122 createItem("Add items on your iPhone")
123 createItem("Edit items on your iPad")
124 createItem("Reorder items on your Mac")
125 createItem("Complete items on your Watch")
126
127 app.typeKey(.escape, modifierFlags: [])
128 usleep(500_000)
129
130
131 let checkbox = itemCheckbox(at: 3)
132 XCTAssertTrue(checkbox.waitForExistence(timeout: 2))
133 checkbox.click()
134 usleep(500_000)
135
136
137 app.typeKey("n", modifierFlags: .command)
138 XCTAssertTrue(draftTextField.waitForExistence(timeout: 2))
139 usleep(300_000)
140
141 saveScreenshot(name: "01-items")
142 }
143
144
145 func testScreenshot02_DarkMode() throws {
146 launchApp(["SCREENSHOT_DARK"])
147
148 createItem("Ask Alfred about utility belt")
149
150 usleep(500_000)
151
152 saveScreenshot(name: "02-click-to-create")
153 }
154
155
156 func testScreenshot03_MultipleSelection() throws {
157 launchApp()
158
159 createItem("Write shell script")
160 createItem("Rewrite shell script")
161 createItem("Question life choices")
162 createItem("Cry tragically")
163 createItem("Achieve catharsis")
164
165 app.typeKey(.escape, modifierFlags: [])
166 usleep(500_000)
167
168
169 navigateToItem(at: 0)
170 usleep(200_000)
171
172
173 cmdClickRow(withText: "Question life choices")
174 cmdClickRow(withText: "Achieve catharsis")
175 usleep(300_000)
176
177 saveScreenshot(name: "03-selection")
178 }
179}