craft-native 0.0.204 → 0.0.206

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,10 @@ import XCTest
4
4
  /// `/script`), one command a line:
5
5
  ///
6
6
  /// launch | activate | terminate | home
7
+ /// notifications Home, then reveal Notification Center's Lock Screen presentation
8
+ /// systemexpect <text> [| secs] / systemabsent <text> / systemtap <text>
9
+ /// assert or tap SpringBoard content, including Live Activities
10
+ /// systemtree <name> SpringBoard accessibility tree → shots/<name>.txt
7
11
  /// background <secs> Home, wait, back to the app
8
12
  /// wait <seconds>
9
13
  /// net offline|online
@@ -38,11 +42,14 @@ import XCTest
38
42
  /// ifid <id> <command…> runs the command only when that element is there (a screen shown the first time)
39
43
  /// log <text>
40
44
  ///
41
- /// Checks log `PASS …` / `FAIL …` lines into driver.log; matrix.sh counts them.
45
+ /// With a bundled journey.txt, evidence is kept as native XCTest attachments
46
+ /// and PASS/FAIL lines appear in the test log. Control-server journeys also
47
+ /// write driver.log; matrix.sh counts those checks.
42
48
  final class DriverTests: XCTestCase {
43
49
  let control = ProcessInfo.processInfo.environment["CRAFT_TEST_CONTROL"] ?? {{CONTROL_URL}}
44
50
  let app = XCUIApplication(bundleIdentifier: {{BUNDLE_ID}})
45
51
  let tabs: [String] = {{TABS}}
52
+ private var bundledJourney = false
46
53
 
47
54
  func get(_ path: String, timeout: Double = 15) -> Data? {
48
55
  var result: Data?
@@ -53,6 +60,15 @@ final class DriverTests: XCTestCase {
53
60
  }
54
61
 
55
62
  func post(_ path: String, _ body: Data) {
63
+ if bundledJourney {
64
+ guard path.hasPrefix("/save?"),
65
+ let name = URLComponents(string: path)?.queryItems?.first(where: { $0.name == "name" })?.value else { return }
66
+ let attachment = XCTAttachment(data: body, uniformTypeIdentifier: name.hasSuffix(".png") ? "public.png" : "public.plain-text")
67
+ attachment.name = name
68
+ attachment.lifetime = .keepAlways
69
+ add(attachment)
70
+ return
71
+ }
56
72
  var request = URLRequest(url: URL(string: control + path)!)
57
73
  request.httpMethod = "POST"
58
74
  request.httpBody = body
@@ -63,7 +79,7 @@ final class DriverTests: XCTestCase {
63
79
 
64
80
  func note(_ text: String) {
65
81
  print("DRIVER \(text)")
66
- post("/note", Data(text.utf8))
82
+ if !bundledJourney { post("/note", Data(text.utf8)) }
67
83
  }
68
84
 
69
85
  func check(_ ok: Bool, _ what: String) {
@@ -71,9 +87,10 @@ final class DriverTests: XCTestCase {
71
87
  XCTAssertTrue(ok, what)
72
88
  }
73
89
 
74
- func element(containing text: String) -> XCUIElement? {
90
+ func element(containing text: String, in target: XCUIApplication? = nil) -> XCUIElement? {
91
+ let root = target ?? app
75
92
  let predicate = NSPredicate(format: "label CONTAINS[c] %@ OR value CONTAINS[c] %@ OR identifier == %@", text, text, text)
76
- for query in [app.buttons, app.links, app.staticTexts, app.otherElements, app.cells, app.textFields] {
93
+ for query in [root.buttons, root.links, root.staticTexts, root.otherElements, root.cells, root.textFields] {
77
94
  let matches = query.matching(predicate)
78
95
  let count = min(matches.count, 25)
79
96
  for index in 0..<count {
@@ -81,7 +98,7 @@ final class DriverTests: XCTestCase {
81
98
  if candidate.exists && candidate.isHittable { return candidate }
82
99
  }
83
100
  }
84
- let any = app.descendants(matching: .any).matching(predicate).firstMatch
101
+ let any = root.descendants(matching: .any).matching(predicate).firstMatch
85
102
  return any.exists ? any : nil
86
103
  }
87
104
 
@@ -141,6 +158,9 @@ final class DriverTests: XCTestCase {
141
158
  }
142
159
 
143
160
  func answerOpenPrompt() {
161
+ // A direct app link has already arrived. Querying an invisible
162
+ // SpringBoard here can stall its accessibility snapshot on iOS 27.
163
+ if app.wait(for: .runningForeground, timeout: 2) { return }
144
164
  let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
145
165
  let open = springboard.buttons["Open"]
146
166
  if open.waitForExistence(timeout: 4) { open.tap() }
@@ -148,7 +168,14 @@ final class DriverTests: XCTestCase {
148
168
 
149
169
  func testScript() throws {
150
170
  continueAfterFailure = true
151
- guard let data = get("/script"), let script = String(data: data, encoding: .utf8) else {
171
+ let data: Data?
172
+ if let resource = Bundle(for: DriverTests.self).url(forResource: "journey", withExtension: "txt") {
173
+ bundledJourney = true
174
+ data = try Data(contentsOf: resource)
175
+ } else {
176
+ data = get("/script")
177
+ }
178
+ guard let data, let script = String(data: data, encoding: .utf8) else {
152
179
  XCTFail("no script")
153
180
  return
154
181
  }
@@ -163,6 +190,10 @@ final class DriverTests: XCTestCase {
163
190
  let parts = line.split(separator: " ", maxSplits: 1).map(String.init)
164
191
  let command = parts[0]
165
192
  let arg = parts.count > 1 ? parts[1] : ""
193
+ if bundledJourney && ["net", "delay", "exec"].contains(command) {
194
+ check(false, "\(command) requires a control-server journey")
195
+ return
196
+ }
166
197
  switch command {
167
198
  case "ifid":
168
199
  let rest = arg.split(separator: " ", maxSplits: 1).map(String.init)
@@ -172,6 +203,27 @@ final class DriverTests: XCTestCase {
172
203
  case "activate": app.activate()
173
204
  case "terminate": app.terminate()
174
205
  case "home": XCUIDevice.shared.press(.home)
206
+ case "notifications":
207
+ XCUIDevice.shared.press(.home)
208
+ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
209
+ let start = springboard.coordinate(withNormalizedOffset: CGVector(dx: 0.1, dy: 0.005))
210
+ let end = springboard.coordinate(withNormalizedOffset: CGVector(dx: 0.1, dy: 0.85))
211
+ start.press(forDuration: 0.1, thenDragTo: end)
212
+ note("notifications: opened")
213
+ case "systemexpect":
214
+ let (text, seconds) = split(arg)
215
+ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
216
+ check(poll(seconds, { element(containing: text, in: springboard) }) != nil, "systemexpect \(text)")
217
+ case "systemabsent":
218
+ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
219
+ check(element(containing: arg, in: springboard) == nil, "systemabsent \(arg)")
220
+ case "systemtap":
221
+ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
222
+ if let target = poll(10, { element(containing: arg, in: springboard) }) { press(target); note("systemtap \(arg): ok") }
223
+ else { XCTFail("System element not found"); note("FAIL systemtap \(arg): NOT FOUND") }
224
+ case "systemtree":
225
+ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
226
+ post("/save?name=\(arg).txt", Data(springboard.debugDescription.utf8))
175
227
  case "background":
176
228
  XCUIDevice.shared.press(.home)
177
229
  Thread.sleep(forTimeInterval: Double(arg) ?? 3)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.204",
3
+ "version": "0.0.206",
4
4
  "type": "module",
5
5
  "description": "Build desktop apps with web languages - TypeScript SDK for Craft",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",