Introducing xcrs-controlkit

Seto Elkahfi committed Jul 30, 2026 at 15:45 UTC 17091d8d975dc59e8474a2998ebe3c7cacf6a7ed
51 files changed +2553
.gitignore new
+7
@@ -0,0 +1,7 @@
1 +.build/
2 +.swiftpm/
3 +Runners/**/build/
4 +DerivedData/
5 +xcuserdata/
6 +*.xcuserstate
7 +.DS_Store
LICENSE new
+190
@@ -0,0 +1,190 @@
1 + Apache License
2 + Version 2.0, January 2004
3 + http://www.apache.org/licenses/
4 +
5 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 +
7 + 1. Definitions.
8 +
9 + "License" shall mean the terms and conditions for use, reproduction,
10 + and distribution as defined by Sections 1 through 9 of this document.
11 +
12 + "Licensor" shall mean the copyright owner or entity authorized by
13 + the copyright owner that is granting the License.
14 +
15 + "Legal Entity" shall mean the union of the acting entity and all
16 + other entities that control, are controlled by, or are under common
17 + control with that entity. For the purposes of this definition,
18 + "control" means (i) the power, direct or indirect, to cause the
19 + direction or management of such entity, whether by contract or
20 + otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 + outstanding shares, or (iii) beneficial ownership of such entity.
22 +
23 + "You" (or "Your") shall mean an individual or Legal Entity
24 + exercising permissions granted by this License.
25 +
26 + "Source" form shall mean the preferred form for making modifications,
27 + including but not limited to software source code, documentation
28 + source, and configuration files.
29 +
30 + "Object" form shall mean any form resulting from mechanical
31 + transformation or translation of a Source form, including but
32 + not limited to compiled object code, generated documentation,
33 + and conversions to other media types.
34 +
35 + "Work" shall mean the work of authorship made available under
36 + the License, as indicated by a copyright notice that is included in
37 + or attached to the work (an example is provided in the Appendix below).
38 +
39 + "Derivative Works" shall mean any work, whether in Source or Object
40 + form, that is based on (or derived from) the Work and for which the
41 + editorial revisions, annotations, elaborations, or other modifications
42 + represent, as a whole, an original work of authorship. For the purposes
43 + of this License, Derivative Works shall not include works that remain
44 + separable from, or merely link (or bind by name) to the interfaces of,
45 + the Work and Derivative Works thereof.
46 +
47 + "Contribution" shall mean, as defined by the copyright owner or by the
48 + Legal Entity authorized to submit on behalf of the copyright owner, any
49 + work of authorship, including the original version of the Work and any
50 + modifications or additions to that Work or Derivative Works of the Work.
51 +
52 + "Contributor" shall mean Licensor and any Legal Entity on behalf of
53 + whom a Contribution has been received by the Licensor and subsequently
54 + incorporated within the Work.
55 +
56 + 2. Grant of Copyright License. Subject to the terms and conditions of
57 + this License, each Contributor hereby grants to You a perpetual,
58 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable
59 + copyright license to reproduce, prepare Derivative Works of,
60 + publicly display, publicly perform, sublicense, and distribute the
61 + Work and such Derivative Works in Source or Object form.
62 +
63 + 3. Grant of Patent License. Subject to the terms and conditions of
64 + this License, each Contributor hereby grants to You a perpetual,
65 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable
66 + (except as stated in this section) patent license to make, have made,
67 + use, offer to sell, sell, import, and otherwise transfer the Work,
68 + where such license applies only to those patent claims licensable
69 + by such Contributor that are necessarily infringed by their
70 + Contribution(s) alone or by combination of their Contribution(s)
71 + with the Work to which such Contribution(s) was submitted. If You
72 + institute patent litigation against any entity (including a
73 + cross-claim or counterclaim in a lawsuit) alleging that the Work
74 + or a Contribution incorporated within the Work constitutes direct
75 + or contributory patent infringement, then any and all patent licenses
76 + granted to You under this License for that Work shall terminate
77 + as of the date such litigation is filed.
78 +
79 + 4. Redistribution. You may reproduce and distribute copies of the
80 + Work or Derivative Works thereof in any medium, with or without
81 + modifications, and in Source or Object form, provided that You
82 + meet the following conditions:
83 +
84 + (a) You must give any other recipients of the Work or
85 + Derivative Works a copy of this License; and
86 +
87 + (b) You must cause any modified files to carry prominent notices
88 + stating that You changed the files; and
89 +
90 + (c) You must retain, in the Source form of any Derivative Works
91 + that You distribute, all copyright, patent, trademark, and
92 + attribution notices from the Source form of the Work,
93 + excluding those notices that do not pertain to any part of
94 + the Derivative Works; and
95 +
96 + (d) If the Work includes a "NOTICE" text file as part of its
97 + distribution, You must include a readable copy of the
98 + attribution notices contained within such NOTICE file, in
99 + at least one of the following places: within a NOTICE text
100 + file distributed as part of the Derivative Works; within
101 + the Source form or documentation, if provided along with the
102 + Derivative Works; or, within a display generated by the
103 + Derivative Works, if and wherever such third-party notices
104 + normally appear. The contents of the NOTICE file are for
105 + informational purposes only and do not modify the License.
106 + You may add Your own attribution notices within Derivative
107 + Works that You distribute, alongside or as an addendum to
108 + the NOTICE text from the Work, provided that such additional
109 + attribution notices cannot be construed as modifying the License.
110 +
111 + You may add Your own license statement for Your modifications and
112 + may provide additional grant of rights to use, copy, modify, merge,
113 + publish, distribute, sublicense, and/or sell copies of the Work,
114 + and to permit persons to whom the Work is furnished to do so,
115 + subject to the following conditions:
116 +
117 + The above copyright notice and this permission notice shall be
118 + included in all copies or substantial portions of the Work.
119 +
120 + 5. Submission of Contributions. Unless You explicitly state otherwise,
121 + any Contribution intentionally submitted for inclusion in the Work
122 + by You to the Licensor shall be under the terms and conditions of
123 + this License, without any additional terms or conditions.
124 + Notwithstanding the above, nothing herein shall supersede or modify
125 + the terms of any separate license agreement you may have executed
126 + with Licensor regarding such Contributions.
127 +
128 + 6. Trademarks. This License does not grant permission to use the trade
129 + names, trademarks, service marks, or product names of the Licensor,
130 + except as required for reasonable and customary use in describing the
131 + origin of the Work and reproducing the content of the NOTICE file.
132 +
133 + 7. Disclaimer of Warranty. Unless required by applicable law or
134 + agreed to in writing, Licensor provides the Work (and each
135 + Contributor provides its Contributions) on an "AS IS" BASIS,
136 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
137 + implied, including, without limitation, any warranties or conditions
138 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
139 + PARTICULAR PURPOSE. You are solely responsible for determining the
140 + appropriateness of using or redistributing the Work and assume any
141 + risks associated with Your exercise of permissions under this License.
142 +
143 + 8. Limitation of Liability. In no event and under no legal theory,
144 + whether in tort (including negligence), contract, or otherwise,
145 + unless required by applicable law (such as deliberate and grossly
146 + negligent acts) or agreed to in writing, shall any Contributor be
147 + liable to You for damages, including any direct, indirect, special,
148 + incidental, or exemplary damages of any character arising as a
149 + result of this License or out of the use or inability to use the
150 + Work (including but not limited to damages for loss of goodwill,
151 + work stoppage, computer failure or malfunction, or all other
152 + commercial damages or losses), even if such Contributor has been
153 + advised of the possibility of such damages.
154 +
155 + 9. Accepting Warranty or Additional Liability. While redistributing
156 + the Work or Derivative Works thereof, You may choose to offer,
157 + and charge a fee for, acceptance of support, warranty, indemnity,
158 + or other liability obligations and/or rights consistent with this
159 + License. However, in accepting such obligations, You may act only
160 + on Your own behalf and on Your sole responsibility, not on behalf
161 + of any other Contributor, and only if You agree to indemnify,
162 + defend, and hold each Contributor harmless for any liability
163 + incurred by, or claims asserted against, such Contributor by reason
164 + of your accepting any such warranty or additional liability.
165 +
166 + END OF TERMS AND CONDITIONS
167 +
168 + APPENDIX: How to apply the Apache License to your work.
169 +
170 + To apply the Apache License to your work, attach the following
171 + boilerplate notice, with the fields enclosed by brackets "[]"
172 + replaced with your own identifying information. (Don't include
173 + the brackets!) The text should be enclosed in the appropriate
174 + comment syntax for the file format in use. Also, it is recommended
175 + that a file or directory name be given on the same line as the
176 + copyright notice for easier identification within third-party archives.
177 +
178 + Copyright 2026 Splitfire AB (smbCloud)
179 +
180 + Licensed under the Apache License, Version 2.0 (the "License");
181 + you may not use this file except in compliance with the License.
182 + You may obtain a copy of the License at
183 +
184 + http://www.apache.org/licenses/LICENSE-2.0
185 +
186 + Unless required by applicable law or agreed to in writing, software
187 + distributed under the License is distributed on an "AS IS" BASIS,
188 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189 + See the License for the specific language governing permissions and
190 + limitations under the License.
Package.swift new
+27
@@ -0,0 +1,27 @@
1 +// swift-tools-version: 5.9
2 +
3 +import PackageDescription
4 +
5 +let package = Package(
6 + name: "XCRSControlKit",
7 + platforms: [
8 + .iOS(.v16),
9 + .macOS(.v13),
10 + .tvOS(.v16),
11 + .watchOS(.v9),
12 + .visionOS(.v1)
13 + ],
14 + products: [
15 + .library(
16 + name: "XCRSControlKit",
17 + targets: ["XCRSControlKit"]
18 + )
19 + ],
20 + targets: [
21 + .target(name: "XCRSControlKit"),
22 + .testTarget(
23 + name: "XCRSControlKitTests",
24 + dependencies: ["XCRSControlKit"]
25 + )
26 + ]
27 +)
README.md new
+64
@@ -0,0 +1,64 @@
1 +# XCRSControlKit
2 +
3 +XCRSControlKit is the shared Swift package contract for black-box automation
4 +of Apple platforms. It provides stable target, capability, and request models
5 +that can be used by native test runners and external automation clients.
6 +
7 +The package is intentionally transport-neutral. Platform runners remain
8 +separate Xcode projects because XCTest and UI testing require platform-specific
9 +schemes, host applications, simulator destinations, signing, and entitlements.
10 +
11 +## Supported platforms
12 +
13 +- iOS and iPadOS
14 +- tvOS
15 +- watchOS
16 +- visionOS
17 +- macOS
18 +
19 +## Installation
20 +
21 +Add the package URL to an Xcode project or Swift package:
22 +
23 +```swift
24 +dependencies: [
25 + .package(url: "https://github.com/smbcloudXYZ/xcrs-controlkit.git", from: "0.1.0")
26 +]
27 +```
28 +
29 +Then import the library:
30 +
31 +```swift
32 +import XCRSControlKit
33 +
34 +let capabilities = XCRSControlKitCapabilities.standard(for: .tvOS)
35 +if capabilities.supports(.remoteButtons) {
36 + // Connect a tvOS runner and send remote-button requests.
37 +}
38 +```
39 +
40 +## Runner projects
41 +
42 +Runner implementations belong outside the Swift package target:
43 +
44 +```text
45 +Runners/
46 +├── iOS/
47 +├── tvOS/
48 +├── watchOS/
49 +├── visionOS/
50 +└── macOS/
51 +```
52 +
53 +Each runner can import `XCRSControlKit` while using the appropriate Apple
54 +frameworks and Xcode test target for its platform.
55 +
56 +## Status
57 +
58 +The package currently provides the public cross-platform contract. Native
59 +runner projects and transport implementations are being added incrementally,
60 +starting with iOS and tvOS.
61 +
62 +## License
63 +
64 +XCRSControlKit is available under the Apache License, Version 2.0.
Runners/Branding/XCRSControlKitLogo.svg new
+29
@@ -0,0 +1,29 @@
1 +<svg
2 + width="512"
3 + height="512"
4 + viewBox="0 0 80 80"
5 + fill="none"
6 + xmlns="http://www.w3.org/2000/svg"
7 +>
8 + <rect x="0" y="0" width="80" height="80" rx="12" fill="#1B4332" />
9 + <text
10 + x="40"
11 + y="38.8"
12 + text-anchor="middle"
13 + font-family="'Helvetica Neue', Helvetica, Arial, sans-serif"
14 + font-weight="800"
15 + font-size="34.4"
16 + letter-spacing="-2"
17 + fill="#F5F3EC"
18 + >XC</text>
19 + <text
20 + x="40"
21 + y="72"
22 + text-anchor="middle"
23 + font-family="'Helvetica Neue', Helvetica, Arial, sans-serif"
24 + font-weight="800"
25 + font-size="34.4"
26 + letter-spacing="-2"
27 + fill="#F5F3EC"
28 + >RS</text>
29 +</svg>
Runners/README.md new
+8
@@ -0,0 +1,8 @@
1 +# Platform runners
2 +
3 +Platform runners are separate Xcode projects rather than SwiftPM targets.
4 +They import the local `XCRSControlKit` package and expose XCTest or
5 +XCUITest-specific automation over the runner transport.
6 +
7 +Use one project per Apple platform so each runner can define its own schemes,
8 +host app, deployment target, signing configuration, and platform capabilities.
Runners/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png
Binary files /dev/null and b/Runners/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png differ
Runners/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new
+14
@@ -0,0 +1,14 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "AppIcon-1024.png",
5 + "idiom" : "universal",
6 + "platform" : "ios",
7 + "size" : "1024x1024"
8 + }
9 + ],
10 + "info" : {
11 + "author" : "xcode",
12 + "version" : 1
13 + }
14 +}
Runners/iOS/ControlKitRPCServer.swift new
+268
@@ -0,0 +1,268 @@
1 +import Foundation
2 +import NIOCore
3 +import NIOHTTP1
4 +import NIOPosix
5 +import XCTest
6 +import XCRSControlKit
7 +
8 +private struct JSONRPCRequest {
9 + let method: String
10 + let params: [String: Any]
11 + let id: Any?
12 +}
13 +
14 +@MainActor
15 +final class ControlKitRPCServer {
16 + private let port: UInt16
17 + private let host: String
18 +
19 + init() {
20 + port = UInt16(ProcessInfo.processInfo.environment["DEVICEKIT_LISTEN_PORT"] ?? "12004") ?? 12004
21 + host = ProcessInfo.processInfo.environment["DEVICEKIT_LISTEN_HOST"] ?? "127.0.0.1"
22 + }
23 +
24 + func start() async throws {
25 + let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
26 + defer {
27 + try? group.syncShutdownGracefully()
28 + }
29 + let requestHandler: (HTTPMethod, String, Data) async -> Data = { [self] method, path, body in
30 + if method == .GET && path == "/health" {
31 + return Data("OK".utf8)
32 + }
33 + return await handle(body)
34 + }
35 + let channel = try await ServerBootstrap(group: group)
36 + .serverChannelOption(ChannelOptions.backlog, value: 256)
37 + .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
38 + .childChannelInitializer { channel in
39 + channel.pipeline.configureHTTPServerPipeline().flatMap {
40 + channel.pipeline.addHandler(RPCHandler(requestHandler: requestHandler))
41 + }
42 + }
43 + .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
44 + .bind(host: host, port: Int(port))
45 + .get()
46 + try await channel.closeFuture.get()
47 + }
48 +
49 + private func handle(_ body: Data) async -> Data {
50 + do {
51 + if body == Data("health".utf8) {
52 + return try JSONSerialization.data(withJSONObject: ["status": "ok"])
53 + }
54 +
55 + guard
56 + let object = try JSONSerialization.jsonObject(with: body) as? [String: Any],
57 + let method = object["method"] as? String
58 + else {
59 + return try response(error: -32600, message: "Invalid Request", id: nil)
60 + }
61 +
62 + let request = JSONRPCRequest(
63 + method: method,
64 + params: object["params"] as? [String: Any] ?? [:],
65 + id: object["id"]
66 + )
67 + return try await dispatch(request)
68 + } catch {
69 + return (try? response(error: -32603, message: error.localizedDescription, id: nil)) ?? Data()
70 + }
71 + }
72 +
73 + private func dispatch(_ request: JSONRPCRequest) async throws -> Data {
74 + switch request.method {
75 + case "device.capabilities":
76 + let platform: XCRSControlKitPlatform = {
77 + #if os(tvOS)
78 + return .tvOS
79 + #else
80 + return .iOS
81 + #endif
82 + }()
83 + return try response(
84 + result: [
85 + "platform": platform.rawValue,
86 + "capabilities": XCRSControlKitCapabilities.standard(for: platform).values.map(\.rawValue).sorted()
87 + ],
88 + id: request.id
89 + )
90 + case "device.info":
91 + return try response(
92 + result: ["port": port, "runner": "XCRSControlKit"],
93 + id: request.id
94 + )
95 + case "device.apps.launch":
96 + let bundleIdentifier = try stringParameter("bundleId", request.params)
97 + let application = XCUIApplication(bundleIdentifier: bundleIdentifier)
98 + application.launch()
99 + return try response(result: ["success": true], id: request.id)
100 + case "device.apps.terminate":
101 + let bundleIdentifier = try stringParameter("bundleId", request.params)
102 + XCUIApplication(bundleIdentifier: bundleIdentifier).terminate()
103 + return try response(result: ["success": true], id: request.id)
104 + case "device.io.tap":
105 + #if os(tvOS)
106 + throw RunnerError.unsupportedButton("touch")
107 + #else
108 + let x = try doubleParameter("x", request.params)
109 + let y = try doubleParameter("y", request.params)
110 + let application = foregroundApplication(request.params)
111 + let frame = application.frame
112 + let coordinate = application.coordinate(
113 + withNormalizedOffset: CGVector(
114 + dx: max(0, min(1, x / Double(frame.width))),
115 + dy: max(0, min(1, y / Double(frame.height)))
116 + )
117 + )
118 + coordinate.tap()
119 + return try response(result: ["success": true], id: request.id)
120 + #endif
121 + case "device.io.text":
122 + let text = try stringParameter("text", request.params)
123 + foregroundApplication(request.params).typeText(text)
124 + return try response(result: ["success": true], id: request.id)
125 + case "device.io.button":
126 + let button = try stringParameter("button", request.params)
127 + #if os(tvOS)
128 + let press: XCUIRemote.Button
129 + switch button {
130 + case "menu":
131 + press = .menu
132 + case "select":
133 + press = .select
134 + case "playPause":
135 + press = .playPause
136 + case "up":
137 + press = .up
138 + case "down":
139 + press = .down
140 + case "left":
141 + press = .left
142 + case "right":
143 + press = .right
144 + default:
145 + throw RunnerError.unsupportedButton(button)
146 + }
147 + XCUIRemote.shared.press(press)
148 + #else
149 + guard button == "home" else {
150 + throw RunnerError.unsupportedButton(button)
151 + }
152 + XCUIDevice.shared.press(.home)
153 + #endif
154 + return try response(result: ["success": true], id: request.id)
155 + default:
156 + return try response(error: -32601, message: "Method not found", id: request.id)
157 + }
158 + }
159 +
160 + private func foregroundApplication(_ params: [String: Any]) -> XCUIApplication {
161 + if let bundleIdentifier = params["bundleId"] as? String {
162 + return XCUIApplication(bundleIdentifier: bundleIdentifier)
163 + }
164 + return XCUIApplication()
165 + }
166 +
167 + private func stringParameter(_ name: String, _ params: [String: Any]) throws -> String {
168 + guard let value = params[name] as? String, !value.isEmpty else {
169 + throw RunnerError.invalidParameter(name)
170 + }
171 + return value
172 + }
173 +
174 + private func doubleParameter(_ name: String, _ params: [String: Any]) throws -> Double {
175 + if let value = params[name] as? Double {
176 + return value
177 + }
178 + if let value = params[name] as? NSNumber {
179 + return value.doubleValue
180 + }
181 + throw RunnerError.invalidParameter(name)
182 + }
183 +
184 + private func response(result: [String: Any], id: Any?) throws -> Data {
185 + try JSONSerialization.data(withJSONObject: [
186 + "jsonrpc": "2.0",
187 + "result": result,
188 + "id": id ?? NSNull()
189 + ])
190 + }
191 +
192 + private func response(error code: Int, message: String, id: Any?) throws -> Data {
193 + try JSONSerialization.data(withJSONObject: [
194 + "jsonrpc": "2.0",
195 + "error": ["code": code, "message": message],
196 + "id": id ?? NSNull()
197 + ])
198 + }
199 +}
200 +
201 +private final class RPCHandler: ChannelInboundHandler {
202 + typealias InboundIn = HTTPServerRequestPart
203 + typealias OutboundOut = HTTPServerResponsePart
204 +
205 + private let requestHandler: (HTTPMethod, String, Data) async -> Data
206 + private var requestHead: HTTPRequestHead?
207 + private var requestBody = Data()
208 +
209 + init(requestHandler: @escaping (HTTPMethod, String, Data) async -> Data) {
210 + self.requestHandler = requestHandler
211 + }
212 +
213 + func channelRead(context: ChannelHandlerContext, data: NIOAny) {
214 + switch unwrapInboundIn(data) {
215 + case .head(let head):
216 + requestHead = head
217 + requestBody.removeAll(keepingCapacity: true)
218 + case .body(var buffer):
219 + if let bytes = buffer.readBytes(length: buffer.readableBytes) {
220 + requestBody.append(contentsOf: bytes)
221 + }
222 + case .end:
223 + guard let requestHead else {
224 + context.close(promise: nil)
225 + return
226 + }
227 +
228 + let method = requestHead.method
229 + let path = requestHead.uri
230 + let body = requestBody
231 + Task {
232 + let responseBody = await requestHandler(method, path, body)
233 + context.eventLoop.execute {
234 + var headers = HTTPHeaders()
235 + headers.add(name: "Content-Type", value: "application/json")
236 + headers.add(name: "Content-Length", value: "\(responseBody.count)")
237 + headers.add(name: "Connection", value: "close")
238 + let responseHead = HTTPResponseHead(
239 + version: requestHead.version,
240 + status: .ok,
241 + headers: headers
242 + )
243 + var buffer = context.channel.allocator.buffer(capacity: responseBody.count)
244 + buffer.writeBytes(responseBody)
245 + context.write(self.wrapOutboundOut(.head(responseHead)), promise: nil)
246 + context.write(self.wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil)
247 + context.writeAndFlush(self.wrapOutboundOut(.end(nil))).whenComplete { _ in
248 + context.close(promise: nil)
249 + }
250 + }
251 + }
252 + }
253 + }
254 +}
255 +
256 +private enum RunnerError: LocalizedError {
257 + case invalidParameter(String)
258 + case unsupportedButton(String)
259 +
260 + var errorDescription: String? {
261 + switch self {
262 + case .invalidParameter(let name):
263 + return "Missing or invalid parameter: \(name)"
264 + case .unsupportedButton(let button):
265 + return "Unsupported iOS button: \(button)"
266 + }
267 + }
268 +}
Runners/iOS/ControlKitRunnerApp.swift new
+114
@@ -0,0 +1,114 @@
1 +import SwiftUI
2 +
3 +@main
4 +struct ControlKitRunnerApp: App {
5 + var body: some Scene {
6 + WindowGroup {
7 + ControlKitShowcaseView()
8 + }
9 + }
10 +}
11 +
12 +private struct ControlKitShowcaseView: View {
13 + private let repositoryURL = URL(string: "https://github.com/ondeinference/xcrs-controlkit")!
14 +
15 + var body: some View {
16 + ZStack {
17 + LinearGradient(
18 + colors: [Color.controlKitGreen, Color(red: 0.04, green: 0.12, blue: 0.09)],
19 + startPoint: .topLeading,
20 + endPoint: .bottomTrailing
21 + )
22 + .ignoresSafeArea()
23 +
24 + ScrollView {
25 + VStack(alignment: .leading, spacing: 28) {
26 + HStack(spacing: 18) {
27 + logo
28 + VStack(alignment: .leading, spacing: 4) {
29 + Text("ControlKit")
30 + .font(.system(size: 40, weight: .bold, design: .rounded))
31 + Text("Apple platform automation")
32 + .font(.title3)
33 + .foregroundStyle(Color.controlKitCream.opacity(0.78))
34 + }
35 + }
36 +
37 + Text("A clean-room runner for reliable black-box testing across Apple platforms.")
38 + .font(.title2.weight(.medium))
39 + .fixedSize(horizontal: false, vertical: true)
40 +
41 + statusCard
42 + informationCard
43 +
44 + Link(destination: repositoryURL) {
45 + Label("View project on GitHub", systemImage: "arrow.up.right")
46 + .font(.headline)
47 + .frame(maxWidth: .infinity)
48 + }
49 + .buttonStyle(.borderedProminent)
50 + .tint(Color.controlKitCream)
51 + .foregroundStyle(Color.controlKitGreen)
52 + }
53 + .foregroundStyle(Color.controlKitCream)
54 + .padding(32)
55 + .frame(maxWidth: 820)
56 + }
57 + .frame(maxWidth: .infinity, maxHeight: .infinity)
58 + }
59 + .frame(maxWidth: .infinity, maxHeight: .infinity)
60 + .frame(minWidth: UIScreen.main.bounds.width, minHeight: UIScreen.main.bounds.height)
61 + .ignoresSafeArea(.all)
62 + .statusBarHidden(true)
63 + }
64 +
65 + private var logo: some View {
66 + Text("XC\nRS")
67 + .font(.system(size: 30, weight: .black, design: .rounded))
68 + .multilineTextAlignment(.center)
69 + .lineSpacing(-8)
70 + .foregroundStyle(Color.controlKitGreen)
71 + .frame(width: 96, height: 96)
72 + .background(Color.controlKitCream, in: RoundedRectangle(cornerRadius: 24))
73 + }
74 +
75 + private var statusCard: some View {
76 + VStack(alignment: .leading, spacing: 14) {
77 + Label("Runner online", systemImage: "checkmark.circle.fill")
78 + .font(.headline)
79 + .foregroundStyle(.green)
80 + Text("The XCTest host is ready to receive ControlKit automation requests.")
81 + .foregroundStyle(Color.controlKitCream.opacity(0.78))
82 + }
83 + .padding(22)
84 + .frame(maxWidth: .infinity, alignment: .leading)
85 + .background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 20))
86 + }
87 +
88 + private var informationCard: some View {
89 + VStack(alignment: .leading, spacing: 16) {
90 + infoRow(title: "Project", value: "XCRSControlKit")
91 + infoRow(title: "Creator", value: "smbCloudXYZ")
92 + infoRow(title: "Platform", value: "iOS and iPadOS")
93 + infoRow(title: "Purpose", value: "Native UI automation runner")
94 + }
95 + .padding(22)
96 + .frame(maxWidth: .infinity, alignment: .leading)
97 + .background(.white.opacity(0.08), in: RoundedRectangle(cornerRadius: 20))
98 + }
99 +
100 + private func infoRow(title: String, value: String) -> some View {
101 + HStack {
102 + Text(title)
103 + .foregroundStyle(Color.controlKitCream.opacity(0.62))
104 + Spacer()
105 + Text(value)
106 + .fontWeight(.semibold)
107 + }
108 + }
109 +}
110 +
111 +private extension Color {
112 + static let controlKitGreen = Color(red: 0.106, green: 0.263, blue: 0.196)
113 + static let controlKitCream = Color(red: 0.961, green: 0.953, blue: 0.925)
114 +}
Runners/iOS/ControlKitRunnerUITests.swift new
+10
@@ -0,0 +1,10 @@
1 +import XCTest
2 +
3 +final class ControlKitRunnerUITests: XCTestCase {
4 + @MainActor
5 + func testRunAutomation() async throws {
6 + continueAfterFailure = true
7 + let server = ControlKitRPCServer()
8 + try await server.start()
9 + }
10 +}
Runners/iOS/README.md new
+9
@@ -0,0 +1,9 @@
1 +# XCRS ControlKit iOS runner
2 +
3 +This is the first native runner owned by XCRS ControlKit. It is an Xcode UI
4 +testing project, not a SwiftPM target, and imports the public
5 +`XCRSControlKit` package for capability and request contracts.
6 +
7 +The runner exposes a small localhost JSON-RPC server to the Rust `xcrs`
8 +client using SwiftNIO's HTTP/1 server pipeline. It intentionally contains no
9 +DeviceKit source or private XCTest headers.
Runners/iOS/XCRSControlKitRunner.xcodeproj/project.pbxproj new
+500
@@ -0,0 +1,500 @@
1 +// !$*UTF8*$!
2 +{
3 + archiveVersion = 1;
4 + classes = {
5 + };
6 + objectVersion = 46;
7 + objects = {
8 +
9 +/* Begin PBXBuildFile section */
10 + 12FB8732E528D5BC3CFD5353 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 677B2BA6304D7A19CA4199C6 /* Foundation.framework */; };
11 + 240C04374372369CF9288310 /* ControlKitRPCServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F4A2788516250C6390D6A7 /* ControlKitRPCServer.swift */; };
12 + 5267368AE4FA4A1AF152F2BB /* ControlKitRunnerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3825CF3F09E2C73AC5BCF561 /* ControlKitRunnerApp.swift */; };
13 + 623F26CA1E5FC3982AAB778C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9FE4276F055DA74C55074CB /* Assets.xcassets */; };
14 + 6E7B19FB8377403D45E5B319 /* ControlKitRunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97053F46DD106789E5704C28 /* ControlKitRunnerUITests.swift */; };
15 + A66D6E921CCD246E49A34BD2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 677B2BA6304D7A19CA4199C6 /* Foundation.framework */; };
16 +/* End PBXBuildFile section */
17 +
18 +/* Begin PBXContainerItemProxy section */
19 + A427DD4B39849849959312A5 /* PBXContainerItemProxy */ = {
20 + isa = PBXContainerItemProxy;
21 + containerPortal = 5A4615FAD9C725BDA6BB9D0D /* Project object */;
22 + proxyType = 1;
23 + remoteGlobalIDString = 65C69F9323FE4A33A9BD5CFD;
24 + remoteInfo = XCRSControlKitRunner;
25 + };
26 +/* End PBXContainerItemProxy section */
27 +
28 +/* Begin PBXFileReference section */
29 + 158D6AE634D750F58BFA4F3C /* XCRSControlKitRunner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XCRSControlKitRunner.app; sourceTree = BUILT_PRODUCTS_DIR; };
30 + 3825CF3F09E2C73AC5BCF561 /* ControlKitRunnerApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControlKitRunnerApp.swift; sourceTree = "<group>"; };
31 + 45F4A2788516250C6390D6A7 /* ControlKitRPCServer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControlKitRPCServer.swift; sourceTree = "<group>"; };
32 + 677B2BA6304D7A19CA4199C6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
33 + 97053F46DD106789E5704C28 /* ControlKitRunnerUITests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControlKitRunnerUITests.swift; sourceTree = "<group>"; };
34 + C9FE4276F055DA74C55074CB /* Assets.xcassets */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
35 + F2384A3BC0E7D978EED69C35 /* XCRSControlKitRunnerUITests */ = {isa = PBXFileReference; includeInIndex = 0; path = XCRSControlKitRunnerUITests; sourceTree = BUILT_PRODUCTS_DIR; };
36 +/* End PBXFileReference section */
37 +
38 +/* Begin PBXFrameworksBuildPhase section */
39 + 80BD8489289F29F80E617598 /* Frameworks */ = {
40 + isa = PBXFrameworksBuildPhase;
41 + buildActionMask = 2147483647;
42 + files = (
43 + A66D6E921CCD246E49A34BD2 /* Foundation.framework in Frameworks */,
44 + );
45 + runOnlyForDeploymentPostprocessing = 0;
46 + };
47 + CE6C87241EBD1B86DA8BA3AF /* Frameworks */ = {
48 + isa = PBXFrameworksBuildPhase;
49 + buildActionMask = 2147483647;
50 + files = (
51 + 12FB8732E528D5BC3CFD5353 /* Foundation.framework in Frameworks */,
52 + );
53 + runOnlyForDeploymentPostprocessing = 0;
54 + };
55 +/* End PBXFrameworksBuildPhase section */
56 +
57 +/* Begin PBXGroup section */
58 + 26FCC7EB76F7FBFA9F2B1238 /* XCRSControlKitRunnerUITests */ = {
59 + isa = PBXGroup;
60 + children = (
61 + 97053F46DD106789E5704C28 /* ControlKitRunnerUITests.swift */,
62 + 45F4A2788516250C6390D6A7 /* ControlKitRPCServer.swift */,
63 + );
64 + name = XCRSControlKitRunnerUITests;
65 + path = .;
66 + sourceTree = "<group>";
67 + };
68 + 27707640A8563F24E4EB7902 /* Frameworks */ = {
69 + isa = PBXGroup;
70 + children = (
71 + F62FFE7A6809A05F068F78F4 /* iOS */,
72 + );
73 + name = Frameworks;
74 + sourceTree = "<group>";
75 + };
76 + 48CB2BAD4FA1AA8E4DA76E10 /* XCRSControlKitRunner */ = {
77 + isa = PBXGroup;
78 + children = (
79 + 3825CF3F09E2C73AC5BCF561 /* ControlKitRunnerApp.swift */,
80 + C9FE4276F055DA74C55074CB /* Assets.xcassets */,
81 + );
82 + name = XCRSControlKitRunner;
83 + path = .;
84 + sourceTree = "<group>";
85 + };
86 + 6B3D128F7499546CFF24AF0B /* Products */ = {
87 + isa = PBXGroup;
88 + children = (
89 + 158D6AE634D750F58BFA4F3C /* XCRSControlKitRunner.app */,
90 + F2384A3BC0E7D978EED69C35 /* XCRSControlKitRunnerUITests */,
91 + );
92 + name = Products;
93 + sourceTree = "<group>";
94 + };
95 + A58B00BE92DC60DE632CF4D2 = {
96 + isa = PBXGroup;
97 + children = (
98 + 6B3D128F7499546CFF24AF0B /* Products */,
99 + 27707640A8563F24E4EB7902 /* Frameworks */,
100 + 48CB2BAD4FA1AA8E4DA76E10 /* XCRSControlKitRunner */,
101 + 26FCC7EB76F7FBFA9F2B1238 /* XCRSControlKitRunnerUITests */,
102 + );
103 + sourceTree = "<group>";
104 + };
105 + F62FFE7A6809A05F068F78F4 /* iOS */ = {
106 + isa = PBXGroup;
107 + children = (
108 + 677B2BA6304D7A19CA4199C6 /* Foundation.framework */,
109 + );
110 + name = iOS;
111 + sourceTree = "<group>";
112 + };
113 +/* End PBXGroup section */
114 +
115 +/* Begin PBXNativeTarget section */
116 + 3D3C633B3F1CE5F513D7F973 /* XCRSControlKitRunnerUITests */ = {
117 + isa = PBXNativeTarget;
118 + buildConfigurationList = 09DCF821A316EB9B782B3281 /* Build configuration list for PBXNativeTarget "XCRSControlKitRunnerUITests" */;
119 + buildPhases = (
120 + BE45964975925FCDB7E07A02 /* Sources */,
121 + CE6C87241EBD1B86DA8BA3AF /* Frameworks */,
122 + 8C5E82B59DC87CA04ED56147 /* Resources */,
123 + );
124 + buildRules = (
125 + );
126 + dependencies = (
127 + AD9C27DDC63B00A39E2F2F6B /* PBXTargetDependency */,
128 + );
129 + name = XCRSControlKitRunnerUITests;
130 + packageProductDependencies = (
131 + 519A8A0C72E51DA6D0174E87 /* XCRSControlKit */,
132 + 3C011EAFF81B128EFF169657 /* NIOCore */,
133 + 3C599300D59533D3F1EAAB06 /* NIOHTTP1 */,
134 + 752918E24D37ADB5D048781D /* NIOPosix */,
135 + );
136 + productName = XCRSControlKitRunnerUITests;
137 + productReference = F2384A3BC0E7D978EED69C35 /* XCRSControlKitRunnerUITests */;
138 + productType = "com.apple.product-type.bundle.ui-testing";
139 + };
140 + 65C69F9323FE4A33A9BD5CFD /* XCRSControlKitRunner */ = {
141 + isa = PBXNativeTarget;
142 + buildConfigurationList = 1E9093BA177C6E09C7249452 /* Build configuration list for PBXNativeTarget "XCRSControlKitRunner" */;
143 + buildPhases = (
144 + 2E147C33CA6023EC566F9503 /* Sources */,
145 + 80BD8489289F29F80E617598 /* Frameworks */,
146 + B06C9835F24B5A268D7376F3 /* Resources */,
147 + );
148 + buildRules = (
149 + );
150 + dependencies = (
151 + );
152 + name = XCRSControlKitRunner;
153 + productName = XCRSControlKitRunner;
154 + productReference = 158D6AE634D750F58BFA4F3C /* XCRSControlKitRunner.app */;
155 + productType = "com.apple.product-type.application";
156 + };
157 +/* End PBXNativeTarget section */
158 +
159 +/* Begin PBXProject section */
160 + 5A4615FAD9C725BDA6BB9D0D /* Project object */ = {
161 + isa = PBXProject;
162 + attributes = {
163 + LastSwiftUpdateCheck = 2630;
164 + LastUpgradeCheck = 2630;
165 + };
166 + buildConfigurationList = 8E50C3CB1D836D41CF571655 /* Build configuration list for PBXProject "XCRSControlKitRunner" */;
167 + compatibilityVersion = "Xcode 3.2";
168 + developmentRegion = en;
169 + hasScannedForEncodings = 0;
170 + knownRegions = (
171 + en,
172 + Base,
173 + );
174 + mainGroup = A58B00BE92DC60DE632CF4D2;
175 + minimizedProjectReferenceProxies = 0;
176 + packageReferences = (
177 + 3DBF8CF0F31AA03CAC623B80 /* XCLocalSwiftPackageReference ".." */,
178 + 5B254FC74CBF06A66B4DD162 /* XCRemoteSwiftPackageReference "swift-nio" */,
179 + );
180 + preferredProjectObjectVersion = 77;
181 + productRefGroup = 6B3D128F7499546CFF24AF0B /* Products */;
182 + projectDirPath = "";
183 + projectRoot = "";
184 + targets = (
185 + 65C69F9323FE4A33A9BD5CFD /* XCRSControlKitRunner */,
186 + 3D3C633B3F1CE5F513D7F973 /* XCRSControlKitRunnerUITests */,
187 + );
188 + };
189 +/* End PBXProject section */
190 +
191 +/* Begin PBXResourcesBuildPhase section */
192 + 8C5E82B59DC87CA04ED56147 /* Resources */ = {
193 + isa = PBXResourcesBuildPhase;
194 + buildActionMask = 2147483647;
195 + files = (
196 + );
197 + runOnlyForDeploymentPostprocessing = 0;
198 + };
199 + B06C9835F24B5A268D7376F3 /* Resources */ = {
200 + isa = PBXResourcesBuildPhase;
201 + buildActionMask = 2147483647;
202 + files = (
203 + 623F26CA1E5FC3982AAB778C /* Assets.xcassets in Resources */,
204 + );
205 + runOnlyForDeploymentPostprocessing = 0;
206 + };
207 +/* End PBXResourcesBuildPhase section */
208 +
209 +/* Begin PBXSourcesBuildPhase section */
210 + 2E147C33CA6023EC566F9503 /* Sources */ = {
211 + isa = PBXSourcesBuildPhase;
212 + buildActionMask = 2147483647;
213 + files = (
214 + 5267368AE4FA4A1AF152F2BB /* ControlKitRunnerApp.swift in Sources */,
215 + );
216 + runOnlyForDeploymentPostprocessing = 0;
217 + };
218 + BE45964975925FCDB7E07A02 /* Sources */ = {
219 + isa = PBXSourcesBuildPhase;
220 + buildActionMask = 2147483647;
221 + files = (
222 + 6E7B19FB8377403D45E5B319 /* ControlKitRunnerUITests.swift in Sources */,
223 + 240C04374372369CF9288310 /* ControlKitRPCServer.swift in Sources */,
224 + );
225 + runOnlyForDeploymentPostprocessing = 0;
226 + };
227 +/* End PBXSourcesBuildPhase section */
228 +
229 +/* Begin PBXTargetDependency section */
230 + AD9C27DDC63B00A39E2F2F6B /* PBXTargetDependency */ = {
231 + isa = PBXTargetDependency;
232 + name = XCRSControlKitRunner;
233 + target = 65C69F9323FE4A33A9BD5CFD /* XCRSControlKitRunner */;
234 + targetProxy = A427DD4B39849849959312A5 /* PBXContainerItemProxy */;
235 + };
236 +/* End PBXTargetDependency section */
237 +
238 +/* Begin XCBuildConfiguration section */
239 + 00ABD475CB5E7B4186EF1F6F /* Debug */ = {
240 + isa = XCBuildConfiguration;
241 + buildSettings = {
242 + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
243 + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
244 + CLANG_ENABLE_OBJC_WEAK = NO;
245 + GENERATE_INFOPLIST_FILE = YES;
246 + INFOPLIST_KEY_CFBundleDisplayName = ControlKit;
247 + INFOPLIST_KEY_UIRequiresFullScreen = YES;
248 + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
249 + IPHONEOS_DEPLOYMENT_TARGET = 16.0;
250 + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
251 + PRODUCT_BUNDLE_IDENTIFIER = "com.smbcloud.xcrs-controlkit.runner";
252 + PRODUCT_NAME = ControlKit;
253 + SDKROOT = iphoneos;
254 + SWIFT_VERSION = 5.9;
255 + TARGETED_DEVICE_FAMILY = "1,2";
256 + };
257 + name = Debug;
258 + };
259 + 2980F2C3DA0326EBDFED9234 /* Release */ = {
260 + isa = XCBuildConfiguration;
261 + buildSettings = {
262 + ALWAYS_SEARCH_USER_PATHS = NO;
263 + CLANG_ANALYZER_NONNULL = YES;
264 + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
265 + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
266 + CLANG_CXX_LIBRARY = "libc++";
267 + CLANG_ENABLE_MODULES = YES;
268 + CLANG_ENABLE_OBJC_ARC = YES;
269 + CLANG_ENABLE_OBJC_WEAK = YES;
270 + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
271 + CLANG_WARN_BOOL_CONVERSION = YES;
272 + CLANG_WARN_COMMA = YES;
273 + CLANG_WARN_CONSTANT_CONVERSION = YES;
274 + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
275 + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
276 + CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
277 + CLANG_WARN_EMPTY_BODY = YES;
278 + CLANG_WARN_ENUM_CONVERSION = YES;
279 + CLANG_WARN_INFINITE_RECURSION = YES;
280 + CLANG_WARN_INT_CONVERSION = YES;
281 + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
282 + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
283 + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
284 + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
285 + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
286 + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
287 + CLANG_WARN_STRICT_PROTOTYPES = YES;
288 + CLANG_WARN_SUSPICIOUS_MOVE = YES;
289 + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
290 + CLANG_WARN_UNREACHABLE_CODE = YES;
291 + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
292 + COPY_PHASE_STRIP = NO;
293 + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
294 + ENABLE_NS_ASSERTIONS = NO;
295 + ENABLE_STRICT_OBJC_MSGSEND = YES;
296 + GCC_C_LANGUAGE_STANDARD = gnu11;
297 + GCC_NO_COMMON_BLOCKS = YES;
298 + GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
299 + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
300 + GCC_WARN_UNDECLARED_SELECTOR = YES;
301 + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
302 + GCC_WARN_UNUSED_FUNCTION = YES;
303 + GCC_WARN_UNUSED_VARIABLE = YES;
304 + MTL_ENABLE_DEBUG_INFO = NO;
305 + MTL_FAST_MATH = YES;
306 + PRODUCT_NAME = "$(TARGET_NAME)";
307 + SWIFT_COMPILATION_MODE = wholemodule;
308 + SWIFT_OPTIMIZATION_LEVEL = "-O";
309 + SWIFT_VERSION = 5.0;
310 + };
311 + name = Release;
312 + };
313 + 720942CCA660A8F8898D0376 /* Debug */ = {
314 + isa = XCBuildConfiguration;
315 + buildSettings = {
316 + CLANG_ENABLE_OBJC_WEAK = NO;
317 + GENERATE_INFOPLIST_FILE = YES;
318 + IPHONEOS_DEPLOYMENT_TARGET = 16.0;
319 + PRODUCT_BUNDLE_IDENTIFIER = "com.smbcloud.xcrs-controlkit.runnerUITests";
320 + PRODUCT_NAME = XCRSControlKitRunnerUITests;
321 + SDKROOT = iphoneos;
322 + SWIFT_VERSION = 5.9;
323 + TARGETED_DEVICE_FAMILY = "1,2";
324 + TEST_TARGET_NAME = XCRSControlKitRunner;
325 + };
326 + name = Debug;
327 + };
328 + A3145EA68D6C9D1145657B84 /* Release */ = {
329 + isa = XCBuildConfiguration;
330 + buildSettings = {
331 + CLANG_ENABLE_OBJC_WEAK = NO;
332 + GENERATE_INFOPLIST_FILE = YES;
333 + IPHONEOS_DEPLOYMENT_TARGET = 16.0;
334 + PRODUCT_BUNDLE_IDENTIFIER = "com.smbcloud.xcrs-controlkit.runnerUITests";
335 + PRODUCT_NAME = XCRSControlKitRunnerUITests;
336 + SDKROOT = iphoneos;
337 + SWIFT_VERSION = 5.9;
338 + TARGETED_DEVICE_FAMILY = "1,2";
339 + TEST_TARGET_NAME = XCRSControlKitRunner;
340 + VALIDATE_PRODUCT = YES;
341 + };
342 + name = Release;
343 + };
344 + CCA6074EF7667C1F23D52908 /* Debug */ = {
345 + isa = XCBuildConfiguration;
346 + buildSettings = {
347 + ALWAYS_SEARCH_USER_PATHS = NO;
348 + CLANG_ANALYZER_NONNULL = YES;
349 + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
350 + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
351 + CLANG_CXX_LIBRARY = "libc++";
352 + CLANG_ENABLE_MODULES = YES;
353 + CLANG_ENABLE_OBJC_ARC = YES;
354 + CLANG_ENABLE_OBJC_WEAK = YES;
355 + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
356 + CLANG_WARN_BOOL_CONVERSION = YES;
357 + CLANG_WARN_COMMA = YES;
358 + CLANG_WARN_CONSTANT_CONVERSION = YES;
359 + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
360 + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
361 + CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
362 + CLANG_WARN_EMPTY_BODY = YES;
363 + CLANG_WARN_ENUM_CONVERSION = YES;
364 + CLANG_WARN_INFINITE_RECURSION = YES;
365 + CLANG_WARN_INT_CONVERSION = YES;
366 + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
367 + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
368 + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
369 + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
370 + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
371 + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
372 + CLANG_WARN_STRICT_PROTOTYPES = YES;
373 + CLANG_WARN_SUSPICIOUS_MOVE = YES;
374 + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
375 + CLANG_WARN_UNREACHABLE_CODE = YES;
376 + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
377 + COPY_PHASE_STRIP = NO;
378 + DEBUG_INFORMATION_FORMAT = dwarf;
379 + ENABLE_STRICT_OBJC_MSGSEND = YES;
380 + ENABLE_TESTABILITY = YES;
381 + GCC_C_LANGUAGE_STANDARD = gnu11;
382 + GCC_DYNAMIC_NO_PIC = NO;
383 + GCC_NO_COMMON_BLOCKS = YES;
384 + GCC_OPTIMIZATION_LEVEL = 0;
385 + GCC_PREPROCESSOR_DEFINITIONS = (
386 + "DEBUG=1",
387 + "$(inherited)",
388 + );
389 + GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
390 + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
391 + GCC_WARN_UNDECLARED_SELECTOR = YES;
392 + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
393 + GCC_WARN_UNUSED_FUNCTION = YES;
394 + GCC_WARN_UNUSED_VARIABLE = YES;
395 + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
396 + MTL_FAST_MATH = YES;
397 + ONLY_ACTIVE_ARCH = YES;
398 + PRODUCT_NAME = "$(TARGET_NAME)";
399 + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
400 + SWIFT_OPTIMIZATION_LEVEL = "-Onone";
401 + SWIFT_VERSION = 5.0;
402 + };
403 + name = Debug;
404 + };
405 + FD6AFD55D263981B035C1B67 /* Release */ = {
406 + isa = XCBuildConfiguration;
407 + buildSettings = {
408 + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
409 + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
410 + CLANG_ENABLE_OBJC_WEAK = NO;
411 + GENERATE_INFOPLIST_FILE = YES;
412 + INFOPLIST_KEY_CFBundleDisplayName = ControlKit;
413 + INFOPLIST_KEY_UIRequiresFullScreen = YES;
414 + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
415 + IPHONEOS_DEPLOYMENT_TARGET = 16.0;
416 + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
417 + PRODUCT_BUNDLE_IDENTIFIER = "com.smbcloud.xcrs-controlkit.runner";
418 + PRODUCT_NAME = ControlKit;
419 + SDKROOT = iphoneos;
420 + SWIFT_VERSION = 5.9;
421 + TARGETED_DEVICE_FAMILY = "1,2";
422 + VALIDATE_PRODUCT = YES;
423 + };
424 + name = Release;
425 + };
426 +/* End XCBuildConfiguration section */
427 +
428 +/* Begin XCConfigurationList section */
429 + 09DCF821A316EB9B782B3281 /* Build configuration list for PBXNativeTarget "XCRSControlKitRunnerUITests" */ = {
430 + isa = XCConfigurationList;
431 + buildConfigurations = (
432 + A3145EA68D6C9D1145657B84 /* Release */,
433 + 720942CCA660A8F8898D0376 /* Debug */,
434 + );
435 + defaultConfigurationIsVisible = 0;
436 + defaultConfigurationName = Release;
437 + };
438 + 1E9093BA177C6E09C7249452 /* Build configuration list for PBXNativeTarget "XCRSControlKitRunner" */ = {
439 + isa = XCConfigurationList;
440 + buildConfigurations = (
441 + FD6AFD55D263981B035C1B67 /* Release */,
442 + 00ABD475CB5E7B4186EF1F6F /* Debug */,
443 + );
444 + defaultConfigurationIsVisible = 0;
445 + defaultConfigurationName = Release;
446 + };
447 + 8E50C3CB1D836D41CF571655 /* Build configuration list for PBXProject "XCRSControlKitRunner" */ = {
448 + isa = XCConfigurationList;
449 + buildConfigurations = (
450 + CCA6074EF7667C1F23D52908 /* Debug */,
451 + 2980F2C3DA0326EBDFED9234 /* Release */,
452 + );
453 + defaultConfigurationIsVisible = 0;
454 + defaultConfigurationName = Release;
455 + };
456 +/* End XCConfigurationList section */
457 +
458 +/* Begin XCLocalSwiftPackageReference section */
459 + 3DBF8CF0F31AA03CAC623B80 /* XCLocalSwiftPackageReference ".." */ = {
460 + isa = XCLocalSwiftPackageReference;
461 + relativePath = ../..;
462 + };
463 +/* End XCLocalSwiftPackageReference section */
464 +
465 +/* Begin XCRemoteSwiftPackageReference section */
466 + 5B254FC74CBF06A66B4DD162 /* XCRemoteSwiftPackageReference "swift-nio" */ = {
467 + isa = XCRemoteSwiftPackageReference;
468 + repositoryURL = "https://github.com/apple/swift-nio.git";
469 + requirement = {
470 + kind = upToNextMajorVersion;
471 + minimumVersion = 2.0.0;
472 + };
473 + };
474 +/* End XCRemoteSwiftPackageReference section */
475 +
476 +/* Begin XCSwiftPackageProductDependency section */
477 + 3C011EAFF81B128EFF169657 /* NIOCore */ = {
478 + isa = XCSwiftPackageProductDependency;
479 + package = 5B254FC74CBF06A66B4DD162 /* XCRemoteSwiftPackageReference "swift-nio" */;
480 + productName = NIOCore;
481 + };
482 + 3C599300D59533D3F1EAAB06 /* NIOHTTP1 */ = {
483 + isa = XCSwiftPackageProductDependency;
484 + package = 5B254FC74CBF06A66B4DD162 /* XCRemoteSwiftPackageReference "swift-nio" */;
485 + productName = NIOHTTP1;
486 + };
487 + 519A8A0C72E51DA6D0174E87 /* XCRSControlKit */ = {
488 + isa = XCSwiftPackageProductDependency;
489 + package = 5B254FC74CBF06A66B4DD162 /* XCRemoteSwiftPackageReference "swift-nio" */;
490 + productName = XCRSControlKit;
491 + };
492 + 752918E24D37ADB5D048781D /* NIOPosix */ = {
493 + isa = XCSwiftPackageProductDependency;
494 + package = 5B254FC74CBF06A66B4DD162 /* XCRemoteSwiftPackageReference "swift-nio" */;
495 + productName = NIOPosix;
496 + };
497 +/* End XCSwiftPackageProductDependency section */
498 + };
499 + rootObject = 5A4615FAD9C725BDA6BB9D0D /* Project object */;
500 +}
Runners/iOS/XCRSControlKitRunner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new
+42
@@ -0,0 +1,42 @@
1 +{
2 + "originHash" : "dc64d5a9a584c1afdd2823a6335adce6e83ebd68e5e5f250156866e5bdc7e973",
3 + "pins" : [
4 + {
5 + "identity" : "swift-atomics",
6 + "kind" : "remoteSourceControl",
7 + "location" : "https://github.com/apple/swift-atomics.git",
8 + "state" : {
9 + "revision" : "0442cb5a3f98ab802acb777929fdb446bda11a34",
10 + "version" : "1.3.1"
11 + }
12 + },
13 + {
14 + "identity" : "swift-collections",
15 + "kind" : "remoteSourceControl",
16 + "location" : "https://github.com/apple/swift-collections.git",
17 + "state" : {
18 + "revision" : "a0cb0954ecb21e4e31b0070e6ed5674e8556685a",
19 + "version" : "1.6.0"
20 + }
21 + },
22 + {
23 + "identity" : "swift-nio",
24 + "kind" : "remoteSourceControl",
25 + "location" : "https://github.com/apple/swift-nio.git",
26 + "state" : {
27 + "revision" : "0b18836bd8b0162e7e17a995a3fbee20ed8f3b2b",
28 + "version" : "2.101.3"
29 + }
30 + },
31 + {
32 + "identity" : "swift-system",
33 + "kind" : "remoteSourceControl",
34 + "location" : "https://github.com/apple/swift-system.git",
35 + "state" : {
36 + "revision" : "50688cacbd41d547e9eb9f7a213542340b7c442b",
37 + "version" : "1.7.5"
38 + }
39 + }
40 + ],
41 + "version" : 3
42 +}
Runners/iOS/XCRSControlKitRunner.xcodeproj/xcshareddata/xcschemes/ControlKit.xcscheme new
+91
@@ -0,0 +1,91 @@
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<Scheme
3 + LastUpgradeVersion = "2660"
4 + version = "1.7">
5 + <BuildAction
6 + parallelizeBuildables = "YES"
7 + buildImplicitDependencies = "YES"
8 + buildArchitectures = "Automatic">
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 = "65C69F9323FE4A33A9BD5CFD"
19 + BuildableName = "ControlKit.app"
20 + BlueprintName = "XCRSControlKitRunner"
21 + ReferencedContainer = "container:XCRSControlKitRunner.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 + shouldAutocreateTestPlan = "YES">
32 + <Testables>
33 + <TestableReference
34 + skipped = "NO">
35 + <BuildableReference
36 + BuildableIdentifier = "primary"
37 + BlueprintIdentifier = "3D3C633B3F1CE5F513D7F973"
38 + BuildableName = "XCRSControlKitRunnerUITests.xctest"
39 + BlueprintName = "XCRSControlKitRunnerUITests"
40 + ReferencedContainer = "container:XCRSControlKitRunner.xcodeproj">
41 + </BuildableReference>
42 + </TestableReference>
43 + </Testables>
44 + </TestAction>
45 + <LaunchAction
46 + buildConfiguration = "Debug"
47 + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
48 + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
49 + launchStyle = "0"
50 + useCustomWorkingDirectory = "NO"
51 + ignoresPersistentStateOnLaunch = "NO"
52 + debugDocumentVersioning = "YES"
53 + debugServiceExtension = "internal"
54 + allowLocationSimulation = "YES"
55 + queueDebuggingEnableBacktraceRecording = "Yes">
56 + <BuildableProductRunnable
57 + runnableDebuggingMode = "0">
58 + <BuildableReference
59 + BuildableIdentifier = "primary"
60 + BlueprintIdentifier = "65C69F9323FE4A33A9BD5CFD"
61 + BuildableName = "ControlKit.app"
62 + BlueprintName = "XCRSControlKitRunner"
63 + ReferencedContainer = "container:XCRSControlKitRunner.xcodeproj">
64 + </BuildableReference>
65 + </BuildableProductRunnable>
66 + </LaunchAction>
67 + <ProfileAction
68 + buildConfiguration = "Release"
69 + shouldUseLaunchSchemeArgsEnv = "YES"
70 + savedToolIdentifier = ""
71 + useCustomWorkingDirectory = "NO"
72 + debugDocumentVersioning = "YES">
73 + <BuildableProductRunnable
74 + runnableDebuggingMode = "0">
75 + <BuildableReference
76 + BuildableIdentifier = "primary"
77 + BlueprintIdentifier = "65C69F9323FE4A33A9BD5CFD"
78 + BuildableName = "ControlKit.app"
79 + BlueprintName = "XCRSControlKitRunner"
80 + ReferencedContainer = "container:XCRSControlKitRunner.xcodeproj">
81 + </BuildableReference>
82 + </BuildableProductRunnable>
83 + </ProfileAction>
84 + <AnalyzeAction
85 + buildConfiguration = "Debug">
86 + </AnalyzeAction>
87 + <ArchiveAction
88 + buildConfiguration = "Release"
89 + revealArchiveInOrganizer = "YES">
90 + </ArchiveAction>
91 +</Scheme>
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new
+17
@@ -0,0 +1,17 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "ControlKit-tvOS.png",
5 + "idiom" : "tv",
6 + "scale" : "1x"
7 + },
8 + {
9 + "idiom" : "tv",
10 + "scale" : "2x"
11 + }
12 + ],
13 + "info" : {
14 + "author" : "xcode",
15 + "version" : 1
16 + }
17 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/ControlKit-tvOS.png
Binary files /dev/null and b/Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/ControlKit-tvOS.png differ
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + }
6 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json new
+11
@@ -0,0 +1,11 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + },
6 + "layers" : [
7 + { "filename" : "Front.imagestacklayer" },
8 + { "filename" : "Middle.imagestacklayer" },
9 + { "filename" : "Back.imagestacklayer" }
10 + ]
11 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new
+17
@@ -0,0 +1,17 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "ControlKit-tvOS.png",
5 + "idiom" : "tv",
6 + "scale" : "1x"
7 + },
8 + {
9 + "idiom" : "tv",
10 + "scale" : "2x"
11 + }
12 + ],
13 + "info" : {
14 + "author" : "xcode",
15 + "version" : 1
16 + }
17 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/ControlKit-tvOS.png
Binary files /dev/null and b/Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/ControlKit-tvOS.png differ
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + }
6 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new
+17
@@ -0,0 +1,17 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "ControlKit-tvOS.png",
5 + "idiom" : "tv",
6 + "scale" : "1x"
7 + },
8 + {
9 + "idiom" : "tv",
10 + "scale" : "2x"
11 + }
12 + ],
13 + "info" : {
14 + "author" : "xcode",
15 + "version" : 1
16 + }
17 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/ControlKit-tvOS.png
Binary files /dev/null and b/Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/ControlKit-tvOS.png differ
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + }
6 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new
+17
@@ -0,0 +1,17 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "ControlKit-tvOS.png",
5 + "idiom" : "tv",
6 + "scale" : "1x"
7 + },
8 + {
9 + "idiom" : "tv",
10 + "scale" : "2x"
11 + }
12 + ],
13 + "info" : {
14 + "author" : "xcode",
15 + "version" : 1
16 + }
17 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/ControlKit-tvOS.png
Binary files /dev/null and b/Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/ControlKit-tvOS.png differ
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + }
6 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json new
+11
@@ -0,0 +1,11 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + },
6 + "layers" : [
7 + { "filename" : "Front.imagestacklayer" },
8 + { "filename" : "Middle.imagestacklayer" },
9 + { "filename" : "Back.imagestacklayer" }
10 + ]
11 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new
+17
@@ -0,0 +1,17 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "ControlKit-tvOS.png",
5 + "idiom" : "tv",
6 + "scale" : "1x"
7 + },
8 + {
9 + "idiom" : "tv",
10 + "scale" : "2x"
11 + }
12 + ],
13 + "info" : {
14 + "author" : "xcode",
15 + "version" : 1
16 + }
17 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/ControlKit-tvOS.png
Binary files /dev/null and b/Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/ControlKit-tvOS.png differ
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + }
6 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new
+17
@@ -0,0 +1,17 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "ControlKit-tvOS.png",
5 + "idiom" : "tv",
6 + "scale" : "1x"
7 + },
8 + {
9 + "idiom" : "tv",
10 + "scale" : "2x"
11 + }
12 + ],
13 + "info" : {
14 + "author" : "xcode",
15 + "version" : 1
16 + }
17 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/ControlKit-tvOS.png
Binary files /dev/null and b/Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/ControlKit-tvOS.png differ
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + }
6 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json new
+32
@@ -0,0 +1,32 @@
1 +{
2 + "assets" : [
3 + {
4 + "filename" : "App Icon - App Store.imagestack",
5 + "idiom" : "tv",
6 + "role" : "primary-app-icon",
7 + "size" : "1280x768"
8 + },
9 + {
10 + "filename" : "App Icon.imagestack",
11 + "idiom" : "tv",
12 + "role" : "primary-app-icon",
13 + "size" : "400x240"
14 + },
15 + {
16 + "filename" : "Top Shelf Image Wide.imageset",
17 + "idiom" : "tv",
18 + "role" : "top-shelf-image-wide",
19 + "size" : "2320x720"
20 + },
21 + {
22 + "filename" : "Top Shelf Image.imageset",
23 + "idiom" : "tv",
24 + "role" : "top-shelf-image",
25 + "size" : "1920x720"
26 + }
27 + ],
28 + "info" : {
29 + "author" : "xcode",
30 + "version" : 1
31 + }
32 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json new
+17
@@ -0,0 +1,17 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "ControlKit-TopShelf-Wide.png",
5 + "idiom" : "tv",
6 + "scale" : "1x"
7 + },
8 + {
9 + "idiom" : "tv",
10 + "scale" : "2x"
11 + }
12 + ],
13 + "info" : {
14 + "author" : "xcode",
15 + "version" : 1
16 + }
17 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/ControlKit-TopShelf-Wide.png
Binary files /dev/null and b/Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/ControlKit-TopShelf-Wide.png differ
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json new
+17
@@ -0,0 +1,17 @@
1 +{
2 + "images" : [
3 + {
4 + "filename" : "ControlKit-TopShelf.png",
5 + "idiom" : "tv",
6 + "scale" : "1x"
7 + },
8 + {
9 + "idiom" : "tv",
10 + "scale" : "2x"
11 + }
12 + ],
13 + "info" : {
14 + "author" : "xcode",
15 + "version" : 1
16 + }
17 +}
Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/ControlKit-TopShelf.png
Binary files /dev/null and b/Runners/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/ControlKit-TopShelf.png differ
Runners/tvOS/Assets.xcassets/Contents.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + }
6 +}
Runners/tvOS/ControlKitRunnerApp.swift new
+98
@@ -0,0 +1,98 @@
1 +import SwiftUI
2 +
3 +@main
4 +struct ControlKitRunnerApp: App {
5 + var body: some Scene {
6 + WindowGroup {
7 + ControlKitShowcaseView()
8 + }
9 + }
10 +}
11 +
12 +private struct ControlKitShowcaseView: View {
13 + var body: some View {
14 + ZStack {
15 + LinearGradient(
16 + colors: [Color.controlKitGreen, Color(red: 0.04, green: 0.12, blue: 0.09)],
17 + startPoint: .topLeading,
18 + endPoint: .bottomTrailing
19 + )
20 + .ignoresSafeArea()
21 +
22 + VStack(alignment: .leading, spacing: 30) {
23 + HStack(spacing: 22) {
24 + logo
25 + VStack(alignment: .leading, spacing: 6) {
26 + Text("ControlKit")
27 + .font(.system(size: 48, weight: .bold, design: .rounded))
28 + Text("Apple platform automation")
29 + .font(.title2)
30 + .foregroundStyle(Color.controlKitCream.opacity(0.78))
31 + }
32 + }
33 +
34 + Text("A clean-room runner for reliable black-box testing across Apple platforms.")
35 + .font(.title2.weight(.medium))
36 + .frame(maxWidth: 800, alignment: .leading)
37 +
38 + HStack(alignment: .top, spacing: 22) {
39 + statusCard
40 + informationCard
41 + }
42 + }
43 + .foregroundStyle(Color.controlKitCream)
44 + .padding(64)
45 + .frame(maxWidth: 1200, alignment: .leading)
46 + }
47 + }
48 +
49 + private var logo: some View {
50 + Text("XC\nRS")
51 + .font(.system(size: 38, weight: .black, design: .rounded))
52 + .multilineTextAlignment(.center)
53 + .lineSpacing(-10)
54 + .foregroundStyle(Color.controlKitGreen)
55 + .frame(width: 124, height: 124)
56 + .background(Color.controlKitCream, in: RoundedRectangle(cornerRadius: 30))
57 + }
58 +
59 + private var statusCard: some View {
60 + VStack(alignment: .leading, spacing: 14) {
61 + Label("Runner online", systemImage: "checkmark.circle.fill")
62 + .font(.headline)
63 + .foregroundStyle(.green)
64 + Text("The XCTest host is ready to receive ControlKit automation requests.")
65 + .foregroundStyle(Color.controlKitCream.opacity(0.78))
66 + }
67 + .padding(26)
68 + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
69 + .background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 24))
70 + }
71 +
72 + private var informationCard: some View {
73 + VStack(alignment: .leading, spacing: 16) {
74 + infoRow(title: "Project", value: "XCRSControlKit")
75 + infoRow(title: "Creator", value: "smbCloudXYZ")
76 + infoRow(title: "Platform", value: "tvOS")
77 + infoRow(title: "Purpose", value: "Native UI automation runner")
78 + }
79 + .padding(26)
80 + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
81 + .background(.white.opacity(0.08), in: RoundedRectangle(cornerRadius: 24))
82 + }
83 +
84 + private func infoRow(title: String, value: String) -> some View {
85 + HStack {
86 + Text(title)
87 + .foregroundStyle(Color.controlKitCream.opacity(0.62))
88 + Spacer()
89 + Text(value)
90 + .fontWeight(.semibold)
91 + }
92 + }
93 +}
94 +
95 +private extension Color {
96 + static let controlKitGreen = Color(red: 0.106, green: 0.263, blue: 0.196)
97 + static let controlKitCream = Color(red: 0.961, green: 0.953, blue: 0.925)
98 +}
Runners/tvOS/ControlKitRunnerUITests.swift new
+10
@@ -0,0 +1,10 @@
1 +import XCTest
2 +
3 +final class ControlKitRunnerUITests: XCTestCase {
4 + @MainActor
5 + func testRunAutomation() async throws {
6 + continueAfterFailure = true
7 + let server = ControlKitRPCServer()
8 + try await server.start()
9 + }
10 +}
Runners/tvOS/README.md new
+9
@@ -0,0 +1,9 @@
1 +# XCRS ControlKit tvOS runner
2 +
3 +This is the native tvOS runner owned by XCRS ControlKit. It is an Xcode UI
4 +testing project, not a SwiftPM target, and imports the public
5 +`XCRSControlKit` package for capability and request contracts.
6 +
7 +The runner exposes a localhost JSON-RPC server to the Rust `xcrs` client using
8 +SwiftNIO's HTTP/1 server pipeline. tvOS remote buttons are handled through
9 +`XCUIRemote`, with no DeviceKit source or private XCTest headers.
Runners/tvOS/XCRSControlKitRunner.xcodeproj/project.pbxproj new
+497
@@ -0,0 +1,497 @@
1 +// !$*UTF8*$!
2 +{
3 + archiveVersion = 1;
4 + classes = {
5 + };
6 + objectVersion = 60;
7 + objects = {
8 +
9 +/* Begin PBXBuildFile section */
10 + 035513224895A0CE4FB0D064 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30183ACB20CEB829E0490ED5 /* Assets.xcassets */; };
11 + 0D569CAD6B368B9B5C36E8BE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EC08D851E6330976D22EA82 /* Foundation.framework */; };
12 + 3580744EA3A51BD112ABA6D9 /* ControlKitRunnerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8BA1384D1001CFCB138C1 /* ControlKitRunnerApp.swift */; };
13 + 42240429301B7F4D00D1B8E9 /* XCRSControlKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8E9294EFB8D77093FD3218B5 /* XCRSControlKit */; };
14 + 4224042A301B7F4D00D1B8E9 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = B0B9B1FF3D723F8C6B727F7D /* NIOCore */; };
15 + 4224042B301B7F4D00D1B8E9 /* NIOHTTP1 in Frameworks */ = {isa = PBXBuildFile; productRef = B7FD539749497B63541C3CA6 /* NIOHTTP1 */; };
16 + 4224042C301B7F4D00D1B8E9 /* NIOPosix in Frameworks */ = {isa = PBXBuildFile; productRef = B81BFC255C5D3B81571A1A59 /* NIOPosix */; };
17 + 5AD6201B303072328B2B1CF3 /* ControlKitRunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675B8D131573D551CBDD5C49 /* ControlKitRunnerUITests.swift */; };
18 + 5F061CEA82FE3FDD1B4FB7CD /* ControlKitRPCServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87228C0BF79EA823E25797A5 /* ControlKitRPCServer.swift */; };
19 + E550F44417BCF134B5F636A8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EC08D851E6330976D22EA82 /* Foundation.framework */; };
20 +/* End PBXBuildFile section */
21 +
22 +/* Begin PBXContainerItemProxy section */
23 + 4072D01C68F474F1230D8E2E /* PBXContainerItemProxy */ = {
24 + isa = PBXContainerItemProxy;
25 + containerPortal = 0B09FAC4377C92AD60C7E93C /* Project object */;
26 + proxyType = 1;
27 + remoteGlobalIDString = 297E6EBCF6A5F1F8FE403639;
28 + remoteInfo = XCRSControlKitRunner;
29 + };
30 +/* End PBXContainerItemProxy section */
31 +
32 +/* Begin PBXFileReference section */
33 + 2EC08D851E6330976D22EA82 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
34 + 30183ACB20CEB829E0490ED5 /* Assets.xcassets */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
35 + 675B8D131573D551CBDD5C49 /* ControlKitRunnerUITests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControlKitRunnerUITests.swift; sourceTree = "<group>"; };
36 + 87228C0BF79EA823E25797A5 /* ControlKitRPCServer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ControlKitRPCServer.swift; path = ../iOS/ControlKitRPCServer.swift; sourceTree = "<group>"; };
37 + A310FFE002FC01D2DF5242A0 /* XCRSControlKitRunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XCRSControlKitRunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
38 + C3B8D9F0D2E11E792710F2B9 /* XCRSControlKitRunner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = XCRSControlKitRunner.app; path = ControlKit.app; sourceTree = BUILT_PRODUCTS_DIR; };
39 + CEC8BA1384D1001CFCB138C1 /* ControlKitRunnerApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControlKitRunnerApp.swift; sourceTree = "<group>"; };
40 +/* End PBXFileReference section */
41 +
42 +/* Begin PBXFrameworksBuildPhase section */
43 + 124443B6555ADFF30C5C0A78 /* Frameworks */ = {
44 + isa = PBXFrameworksBuildPhase;
45 + buildActionMask = 2147483647;
46 + files = (
47 + 4224042C301B7F4D00D1B8E9 /* NIOPosix in Frameworks */,
48 + 4224042B301B7F4D00D1B8E9 /* NIOHTTP1 in Frameworks */,
49 + 4224042A301B7F4D00D1B8E9 /* NIOCore in Frameworks */,
50 + 42240429301B7F4D00D1B8E9 /* XCRSControlKit in Frameworks */,
51 + 0D569CAD6B368B9B5C36E8BE /* Foundation.framework in Frameworks */,
52 + );
53 + runOnlyForDeploymentPostprocessing = 0;
54 + };
55 + 4D5B6A5287328D2C1161CAD0 /* Frameworks */ = {
56 + isa = PBXFrameworksBuildPhase;
57 + buildActionMask = 2147483647;
58 + files = (
59 + E550F44417BCF134B5F636A8 /* Foundation.framework in Frameworks */,
60 + );
61 + runOnlyForDeploymentPostprocessing = 0;
62 + };
63 +/* End PBXFrameworksBuildPhase section */
64 +
65 +/* Begin PBXGroup section */
66 + 69B76CD159FC79D016CC57A1 /* XCRSControlKitRunnerUITests */ = {
67 + isa = PBXGroup;
68 + children = (
69 + 675B8D131573D551CBDD5C49 /* ControlKitRunnerUITests.swift */,
70 + 87228C0BF79EA823E25797A5 /* ControlKitRPCServer.swift */,
71 + );
72 + name = XCRSControlKitRunnerUITests;
73 + sourceTree = "<group>";
74 + };
75 + 6ACA23F5AEF94EEAA4DD0680 /* Products */ = {
76 + isa = PBXGroup;
77 + children = (
78 + C3B8D9F0D2E11E792710F2B9 /* XCRSControlKitRunner.app */,
79 + A310FFE002FC01D2DF5242A0 /* XCRSControlKitRunnerUITests.xctest */,
80 + );
81 + name = Products;
82 + sourceTree = "<group>";
83 + };
84 + 762B4572EF9B0155C2D74F08 /* Frameworks */ = {
85 + isa = PBXGroup;
86 + children = (
87 + DD999D2C8FCBE1D3F9F52834 /* tvOS */,
88 + );
89 + name = Frameworks;
90 + sourceTree = "<group>";
91 + };
92 + 79823D8EFB85505638E6F389 /* XCRSControlKitRunner */ = {
93 + isa = PBXGroup;
94 + children = (
95 + CEC8BA1384D1001CFCB138C1 /* ControlKitRunnerApp.swift */,
96 + 30183ACB20CEB829E0490ED5 /* Assets.xcassets */,
97 + );
98 + name = XCRSControlKitRunner;
99 + sourceTree = "<group>";
100 + };
101 + CBAFCCCB076AD09654393A3B = {
102 + isa = PBXGroup;
103 + children = (
104 + 762B4572EF9B0155C2D74F08 /* Frameworks */,
105 + 79823D8EFB85505638E6F389 /* XCRSControlKitRunner */,
106 + 69B76CD159FC79D016CC57A1 /* XCRSControlKitRunnerUITests */,
107 + );
108 + sourceTree = "<group>";
109 + };
110 + DD999D2C8FCBE1D3F9F52834 /* tvOS */ = {
111 + isa = PBXGroup;
112 + children = (
113 + 2EC08D851E6330976D22EA82 /* Foundation.framework */,
114 + );
115 + name = tvOS;
116 + sourceTree = "<group>";
117 + };
118 +/* End PBXGroup section */
119 +
120 +/* Begin PBXNativeTarget section */
121 + 297E6EBCF6A5F1F8FE403639 /* XCRSControlKitRunner */ = {
122 + isa = PBXNativeTarget;
123 + buildConfigurationList = 89DAAF59F302E76CB2702737 /* Build configuration list for PBXNativeTarget "XCRSControlKitRunner" */;
124 + buildPhases = (
125 + C5901C5F5EE82CE5F1CDFAAF /* Sources */,
126 + 4D5B6A5287328D2C1161CAD0 /* Frameworks */,
127 + 33EE014E63B28349A2846990 /* Resources */,
128 + );
129 + buildRules = (
130 + );
131 + dependencies = (
132 + );
133 + name = XCRSControlKitRunner;
134 + productName = XCRSControlKitRunner;
135 + productReference = C3B8D9F0D2E11E792710F2B9 /* XCRSControlKitRunner.app */;
136 + productType = "com.apple.product-type.application";
137 + };
138 + 40F02CDFEAF28D1FA9935842 /* XCRSControlKitRunnerUITests */ = {
139 + isa = PBXNativeTarget;
140 + buildConfigurationList = B71370B2451E9AC83FC63C82 /* Build configuration list for PBXNativeTarget "XCRSControlKitRunnerUITests" */;
141 + buildPhases = (
142 + D43ABD22F352B6001DBD40E8 /* Sources */,
143 + 124443B6555ADFF30C5C0A78 /* Frameworks */,
144 + BB611355B972490541A99EA9 /* Resources */,
145 + );
146 + buildRules = (
147 + );
148 + dependencies = (
149 + ED17078DE028C0301457A09B /* PBXTargetDependency */,
150 + );
151 + name = XCRSControlKitRunnerUITests;
152 + packageProductDependencies = (
153 + 8E9294EFB8D77093FD3218B5 /* XCRSControlKit */,
154 + B0B9B1FF3D723F8C6B727F7D /* NIOCore */,
155 + B7FD539749497B63541C3CA6 /* NIOHTTP1 */,
156 + B81BFC255C5D3B81571A1A59 /* NIOPosix */,
157 + );
158 + productName = XCRSControlKitRunnerUITests;
159 + productReference = A310FFE002FC01D2DF5242A0 /* XCRSControlKitRunnerUITests.xctest */;
160 + productType = "com.apple.product-type.bundle.ui-testing";
161 + };
162 +/* End PBXNativeTarget section */
163 +
164 +/* Begin PBXProject section */
165 + 0B09FAC4377C92AD60C7E93C /* Project object */ = {
166 + isa = PBXProject;
167 + attributes = {
168 + LastSwiftUpdateCheck = 2630;
169 + LastUpgradeCheck = 2630;
170 + };
171 + buildConfigurationList = D057C3520D2F6DF298BDCE07 /* Build configuration list for PBXProject "XCRSControlKitRunner" */;
172 + compatibilityVersion = "Xcode 3.2";
173 + developmentRegion = en;
174 + hasScannedForEncodings = 0;
175 + knownRegions = (
176 + en,
177 + Base,
178 + );
179 + mainGroup = CBAFCCCB076AD09654393A3B;
180 + packageReferences = (
181 + 62D4A3BB7E58C0EEDFBD3798 /* XCLocalSwiftPackageReference "../.." */,
182 + 33CCE93A21C53FF1112828ED /* XCRemoteSwiftPackageReference "swift-nio" */,
183 + );
184 + productRefGroup = 6ACA23F5AEF94EEAA4DD0680 /* Products */;
185 + projectDirPath = "";
186 + projectRoot = "";
187 + targets = (
188 + 297E6EBCF6A5F1F8FE403639 /* XCRSControlKitRunner */,
189 + 40F02CDFEAF28D1FA9935842 /* XCRSControlKitRunnerUITests */,
190 + );
191 + };
192 +/* End PBXProject section */
193 +
194 +/* Begin PBXResourcesBuildPhase section */
195 + 33EE014E63B28349A2846990 /* Resources */ = {
196 + isa = PBXResourcesBuildPhase;
197 + buildActionMask = 2147483647;
198 + files = (
199 + 035513224895A0CE4FB0D064 /* Assets.xcassets in Resources */,
200 + );
201 + runOnlyForDeploymentPostprocessing = 0;
202 + };
203 + BB611355B972490541A99EA9 /* Resources */ = {
204 + isa = PBXResourcesBuildPhase;
205 + buildActionMask = 2147483647;
206 + files = (
207 + );
208 + runOnlyForDeploymentPostprocessing = 0;
209 + };
210 +/* End PBXResourcesBuildPhase section */
211 +
212 +/* Begin PBXSourcesBuildPhase section */
213 + C5901C5F5EE82CE5F1CDFAAF /* Sources */ = {
214 + isa = PBXSourcesBuildPhase;
215 + buildActionMask = 2147483647;
216 + files = (
217 + 3580744EA3A51BD112ABA6D9 /* ControlKitRunnerApp.swift in Sources */,
218 + );
219 + runOnlyForDeploymentPostprocessing = 0;
220 + };
221 + D43ABD22F352B6001DBD40E8 /* Sources */ = {
222 + isa = PBXSourcesBuildPhase;
223 + buildActionMask = 2147483647;
224 + files = (
225 + 5AD6201B303072328B2B1CF3 /* ControlKitRunnerUITests.swift in Sources */,
226 + 5F061CEA82FE3FDD1B4FB7CD /* ControlKitRPCServer.swift in Sources */,
227 + );
228 + runOnlyForDeploymentPostprocessing = 0;
229 + };
230 +/* End PBXSourcesBuildPhase section */
231 +
232 +/* Begin PBXTargetDependency section */
233 + ED17078DE028C0301457A09B /* PBXTargetDependency */ = {
234 + isa = PBXTargetDependency;
235 + name = XCRSControlKitRunner;
236 + target = 297E6EBCF6A5F1F8FE403639 /* XCRSControlKitRunner */;
237 + targetProxy = 4072D01C68F474F1230D8E2E /* PBXContainerItemProxy */;
238 + };
239 +/* End PBXTargetDependency section */
240 +
241 +/* Begin XCBuildConfiguration section */
242 + 1E279C47A0AE151A4005B2B0 /* Debug */ = {
243 + isa = XCBuildConfiguration;
244 + buildSettings = {
245 + GENERATE_INFOPLIST_FILE = YES;
246 + PRODUCT_BUNDLE_IDENTIFIER = "com.smbcloud.xcrs-controlkit.tvos.runnerUITests";
247 + PRODUCT_NAME = XCRSControlKitRunnerUITests;
248 + SDKROOT = appletvos;
249 + SWIFT_VERSION = 5.9;
250 + TVOS_DEPLOYMENT_TARGET = 16.0;
251 + };
252 + name = Debug;
253 + };
254 + 20B98D730FB57BAC0F7745B2 /* Release */ = {
255 + isa = XCBuildConfiguration;
256 + buildSettings = {
257 + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
258 + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
259 + GENERATE_INFOPLIST_FILE = YES;
260 + INFOPLIST_KEY_CFBundleDisplayName = ControlKit;
261 + LD_RUNPATH_SEARCH_PATHS = (
262 + "$(inherited)",
263 + "@executable_path/Frameworks",
264 + );
265 + PRODUCT_BUNDLE_IDENTIFIER = "com.smbcloud.xcrs-controlkit.tvos.runner";
266 + PRODUCT_NAME = ControlKit;
267 + SDKROOT = appletvos;
268 + SWIFT_VERSION = 5.9;
269 + TARGETED_DEVICE_FAMILY = 3;
270 + TVOS_DEPLOYMENT_TARGET = 16.0;
271 + VALIDATE_PRODUCT = YES;
272 + };
273 + name = Release;
274 + };
275 + 2D3CFBA5256921E446A203DF /* Release */ = {
276 + isa = XCBuildConfiguration;
277 + buildSettings = {
278 + ALWAYS_SEARCH_USER_PATHS = NO;
279 + CLANG_ANALYZER_NONNULL = YES;
280 + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
281 + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
282 + CLANG_CXX_LIBRARY = "libc++";
283 + CLANG_ENABLE_MODULES = YES;
284 + CLANG_ENABLE_OBJC_ARC = YES;
285 + CLANG_ENABLE_OBJC_WEAK = YES;
286 + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
287 + CLANG_WARN_BOOL_CONVERSION = YES;
288 + CLANG_WARN_COMMA = YES;
289 + CLANG_WARN_CONSTANT_CONVERSION = YES;
290 + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
291 + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
292 + CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
293 + CLANG_WARN_EMPTY_BODY = YES;
294 + CLANG_WARN_ENUM_CONVERSION = YES;
295 + CLANG_WARN_INFINITE_RECURSION = YES;
296 + CLANG_WARN_INT_CONVERSION = YES;
297 + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
298 + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
299 + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
300 + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
301 + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
302 + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
303 + CLANG_WARN_STRICT_PROTOTYPES = YES;
304 + CLANG_WARN_SUSPICIOUS_MOVE = YES;
305 + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
306 + CLANG_WARN_UNREACHABLE_CODE = YES;
307 + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
308 + COPY_PHASE_STRIP = NO;
309 + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
310 + ENABLE_NS_ASSERTIONS = NO;
311 + ENABLE_STRICT_OBJC_MSGSEND = YES;
312 + GCC_C_LANGUAGE_STANDARD = gnu11;
313 + GCC_NO_COMMON_BLOCKS = YES;
314 + GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
315 + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
316 + GCC_WARN_UNDECLARED_SELECTOR = YES;
317 + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
318 + GCC_WARN_UNUSED_FUNCTION = YES;
319 + GCC_WARN_UNUSED_VARIABLE = YES;
320 + MTL_ENABLE_DEBUG_INFO = NO;
321 + MTL_FAST_MATH = YES;
322 + PRODUCT_NAME = "$(TARGET_NAME)";
323 + SWIFT_COMPILATION_MODE = wholemodule;
324 + SWIFT_OPTIMIZATION_LEVEL = "-O";
325 + SWIFT_VERSION = 5.0;
326 + };
327 + name = Release;
328 + };
329 + B86BCF1CBC84DDBB0482F50E /* Release */ = {
330 + isa = XCBuildConfiguration;
331 + buildSettings = {
332 + GENERATE_INFOPLIST_FILE = YES;
333 + PRODUCT_BUNDLE_IDENTIFIER = "com.smbcloud.xcrs-controlkit.tvos.runnerUITests";
334 + PRODUCT_NAME = XCRSControlKitRunnerUITests;
335 + SDKROOT = appletvos;
336 + SWIFT_VERSION = 5.9;
337 + TVOS_DEPLOYMENT_TARGET = 16.0;
338 + VALIDATE_PRODUCT = YES;
339 + };
340 + name = Release;
341 + };
342 + EDCF57E98B03BA7A29CF004B /* Debug */ = {
343 + isa = XCBuildConfiguration;
344 + buildSettings = {
345 + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
346 + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
347 + GENERATE_INFOPLIST_FILE = YES;
348 + INFOPLIST_KEY_CFBundleDisplayName = ControlKit;
349 + LD_RUNPATH_SEARCH_PATHS = (
350 + "$(inherited)",
351 + "@executable_path/Frameworks",
352 + );
353 + PRODUCT_BUNDLE_IDENTIFIER = "com.smbcloud.xcrs-controlkit.tvos.runner";
354 + PRODUCT_NAME = ControlKit;
355 + SDKROOT = appletvos;
356 + SWIFT_VERSION = 5.9;
357 + TARGETED_DEVICE_FAMILY = 3;
358 + TVOS_DEPLOYMENT_TARGET = 16.0;
359 + };
360 + name = Debug;
361 + };
362 + EE217233EFC84E56C7BF8952 /* Debug */ = {
363 + isa = XCBuildConfiguration;
364 + buildSettings = {
365 + ALWAYS_SEARCH_USER_PATHS = NO;
366 + CLANG_ANALYZER_NONNULL = YES;
367 + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
368 + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
369 + CLANG_CXX_LIBRARY = "libc++";
370 + CLANG_ENABLE_MODULES = YES;
371 + CLANG_ENABLE_OBJC_ARC = YES;
372 + CLANG_ENABLE_OBJC_WEAK = YES;
373 + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
374 + CLANG_WARN_BOOL_CONVERSION = YES;
375 + CLANG_WARN_COMMA = YES;
376 + CLANG_WARN_CONSTANT_CONVERSION = YES;
377 + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
378 + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
379 + CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
380 + CLANG_WARN_EMPTY_BODY = YES;
381 + CLANG_WARN_ENUM_CONVERSION = YES;
382 + CLANG_WARN_INFINITE_RECURSION = YES;
383 + CLANG_WARN_INT_CONVERSION = YES;
384 + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
385 + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
386 + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
387 + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
388 + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
389 + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
390 + CLANG_WARN_STRICT_PROTOTYPES = YES;
391 + CLANG_WARN_SUSPICIOUS_MOVE = YES;
392 + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
393 + CLANG_WARN_UNREACHABLE_CODE = YES;
394 + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
395 + COPY_PHASE_STRIP = NO;
396 + DEBUG_INFORMATION_FORMAT = dwarf;
397 + ENABLE_STRICT_OBJC_MSGSEND = YES;
398 + ENABLE_TESTABILITY = YES;
399 + GCC_C_LANGUAGE_STANDARD = gnu11;
400 + GCC_DYNAMIC_NO_PIC = NO;
401 + GCC_NO_COMMON_BLOCKS = YES;
402 + GCC_OPTIMIZATION_LEVEL = 0;
403 + GCC_PREPROCESSOR_DEFINITIONS = (
404 + "DEBUG=1",
405 + "$(inherited)",
406 + );
407 + GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
408 + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
409 + GCC_WARN_UNDECLARED_SELECTOR = YES;
410 + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
411 + GCC_WARN_UNUSED_FUNCTION = YES;
412 + GCC_WARN_UNUSED_VARIABLE = YES;
413 + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
414 + MTL_FAST_MATH = YES;
415 + ONLY_ACTIVE_ARCH = YES;
416 + PRODUCT_NAME = "$(TARGET_NAME)";
417 + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
418 + SWIFT_OPTIMIZATION_LEVEL = "-Onone";
419 + SWIFT_VERSION = 5.0;
420 + };
421 + name = Debug;
422 + };
423 +/* End XCBuildConfiguration section */
424 +
425 +/* Begin XCConfigurationList section */
426 + 89DAAF59F302E76CB2702737 /* Build configuration list for PBXNativeTarget "XCRSControlKitRunner" */ = {
427 + isa = XCConfigurationList;
428 + buildConfigurations = (
429 + 20B98D730FB57BAC0F7745B2 /* Release */,
430 + EDCF57E98B03BA7A29CF004B /* Debug */,
431 + );
432 + defaultConfigurationIsVisible = 0;
433 + defaultConfigurationName = Release;
434 + };
435 + B71370B2451E9AC83FC63C82 /* Build configuration list for PBXNativeTarget "XCRSControlKitRunnerUITests" */ = {
436 + isa = XCConfigurationList;
437 + buildConfigurations = (
438 + B86BCF1CBC84DDBB0482F50E /* Release */,
439 + 1E279C47A0AE151A4005B2B0 /* Debug */,
440 + );
441 + defaultConfigurationIsVisible = 0;
442 + defaultConfigurationName = Release;
443 + };
444 + D057C3520D2F6DF298BDCE07 /* Build configuration list for PBXProject "XCRSControlKitRunner" */ = {
445 + isa = XCConfigurationList;
446 + buildConfigurations = (
447 + EE217233EFC84E56C7BF8952 /* Debug */,
448 + 2D3CFBA5256921E446A203DF /* Release */,
449 + );
450 + defaultConfigurationIsVisible = 0;
451 + defaultConfigurationName = Release;
452 + };
453 +/* End XCConfigurationList section */
454 +
455 +/* Begin XCLocalSwiftPackageReference section */
456 + 62D4A3BB7E58C0EEDFBD3798 /* XCLocalSwiftPackageReference "../.." */ = {
457 + isa = XCLocalSwiftPackageReference;
458 + relativePath = ../..;
459 + };
460 +/* End XCLocalSwiftPackageReference section */
461 +
462 +/* Begin XCRemoteSwiftPackageReference section */
463 + 33CCE93A21C53FF1112828ED /* XCRemoteSwiftPackageReference "swift-nio" */ = {
464 + isa = XCRemoteSwiftPackageReference;
465 + repositoryURL = "https://github.com/apple/swift-nio.git";
466 + requirement = {
467 + kind = upToNextMajorVersion;
468 + minimumVersion = 2.0.0;
469 + };
470 + };
471 +/* End XCRemoteSwiftPackageReference section */
472 +
473 +/* Begin XCSwiftPackageProductDependency section */
474 + 8E9294EFB8D77093FD3218B5 /* XCRSControlKit */ = {
475 + isa = XCSwiftPackageProductDependency;
476 + package = 33CCE93A21C53FF1112828ED /* XCRemoteSwiftPackageReference "swift-nio" */;
477 + productName = XCRSControlKit;
478 + };
479 + B0B9B1FF3D723F8C6B727F7D /* NIOCore */ = {
480 + isa = XCSwiftPackageProductDependency;
481 + package = 33CCE93A21C53FF1112828ED /* XCRemoteSwiftPackageReference "swift-nio" */;
482 + productName = NIOCore;
483 + };
484 + B7FD539749497B63541C3CA6 /* NIOHTTP1 */ = {
485 + isa = XCSwiftPackageProductDependency;
486 + package = 33CCE93A21C53FF1112828ED /* XCRemoteSwiftPackageReference "swift-nio" */;
487 + productName = NIOHTTP1;
488 + };
489 + B81BFC255C5D3B81571A1A59 /* NIOPosix */ = {
490 + isa = XCSwiftPackageProductDependency;
491 + package = 33CCE93A21C53FF1112828ED /* XCRemoteSwiftPackageReference "swift-nio" */;
492 + productName = NIOPosix;
493 + };
494 +/* End XCSwiftPackageProductDependency section */
495 + };
496 + rootObject = 0B09FAC4377C92AD60C7E93C /* Project object */;
497 +}
Runners/tvOS/XCRSControlKitRunner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new
+8
@@ -0,0 +1,8 @@
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 +<plist version="1.0">
4 +<dict>
5 + <key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
6 + <true/>
7 +</dict>
8 +</plist>
Runners/tvOS/XCRSControlKitRunner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new
+42
@@ -0,0 +1,42 @@
1 +{
2 + "originHash" : "dc64d5a9a584c1afdd2823a6335adce6e83ebd68e5e5f250156866e5bdc7e973",
3 + "pins" : [
4 + {
5 + "identity" : "swift-atomics",
6 + "kind" : "remoteSourceControl",
7 + "location" : "https://github.com/apple/swift-atomics.git",
8 + "state" : {
9 + "revision" : "0442cb5a3f98ab802acb777929fdb446bda11a34",
10 + "version" : "1.3.1"
11 + }
12 + },
13 + {
14 + "identity" : "swift-collections",
15 + "kind" : "remoteSourceControl",
16 + "location" : "https://github.com/apple/swift-collections.git",
17 + "state" : {
18 + "revision" : "a0cb0954ecb21e4e31b0070e6ed5674e8556685a",
19 + "version" : "1.6.0"
20 + }
21 + },
22 + {
23 + "identity" : "swift-nio",
24 + "kind" : "remoteSourceControl",
25 + "location" : "https://github.com/apple/swift-nio.git",
26 + "state" : {
27 + "revision" : "0b18836bd8b0162e7e17a995a3fbee20ed8f3b2b",
28 + "version" : "2.101.3"
29 + }
30 + },
31 + {
32 + "identity" : "swift-system",
33 + "kind" : "remoteSourceControl",
34 + "location" : "https://github.com/apple/swift-system.git",
35 + "state" : {
36 + "revision" : "50688cacbd41d547e9eb9f7a213542340b7c442b",
37 + "version" : "1.7.5"
38 + }
39 + }
40 + ],
41 + "version" : 3
42 +}
Runners/tvOS/XCRSControlKitRunner.xcodeproj/xcshareddata/xcschemes/ControlKit.xcscheme new
+91
@@ -0,0 +1,91 @@
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<Scheme
3 + LastUpgradeVersion = "2660"
4 + version = "1.7">
5 + <BuildAction
6 + parallelizeBuildables = "YES"
7 + buildImplicitDependencies = "YES"
8 + buildArchitectures = "Automatic">
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 = "297E6EBCF6A5F1F8FE403639"
19 + BuildableName = "ControlKit.app"
20 + BlueprintName = "XCRSControlKitRunner"
21 + ReferencedContainer = "container:XCRSControlKitRunner.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 + shouldAutocreateTestPlan = "YES">
32 + <Testables>
33 + <TestableReference
34 + skipped = "NO">
35 + <BuildableReference
36 + BuildableIdentifier = "primary"
37 + BlueprintIdentifier = "40F02CDFEAF28D1FA9935842"
38 + BuildableName = "XCRSControlKitRunnerUITests.xctest"
39 + BlueprintName = "XCRSControlKitRunnerUITests"
40 + ReferencedContainer = "container:XCRSControlKitRunner.xcodeproj">
41 + </BuildableReference>
42 + </TestableReference>
43 + </Testables>
44 + </TestAction>
45 + <LaunchAction
46 + buildConfiguration = "Debug"
47 + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
48 + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
49 + launchStyle = "0"
50 + useCustomWorkingDirectory = "NO"
51 + ignoresPersistentStateOnLaunch = "NO"
52 + debugDocumentVersioning = "YES"
53 + debugServiceExtension = "internal"
54 + allowLocationSimulation = "YES"
55 + queueDebuggingEnableBacktraceRecording = "Yes">
56 + <BuildableProductRunnable
57 + runnableDebuggingMode = "0">
58 + <BuildableReference
59 + BuildableIdentifier = "primary"
60 + BlueprintIdentifier = "297E6EBCF6A5F1F8FE403639"
61 + BuildableName = "ControlKit.app"
62 + BlueprintName = "XCRSControlKitRunner"
63 + ReferencedContainer = "container:XCRSControlKitRunner.xcodeproj">
64 + </BuildableReference>
65 + </BuildableProductRunnable>
66 + </LaunchAction>
67 + <ProfileAction
68 + buildConfiguration = "Release"
69 + shouldUseLaunchSchemeArgsEnv = "YES"
70 + savedToolIdentifier = ""
71 + useCustomWorkingDirectory = "NO"
72 + debugDocumentVersioning = "YES">
73 + <BuildableProductRunnable
74 + runnableDebuggingMode = "0">
75 + <BuildableReference
76 + BuildableIdentifier = "primary"
77 + BlueprintIdentifier = "297E6EBCF6A5F1F8FE403639"
78 + BuildableName = "ControlKit.app"
79 + BlueprintName = "XCRSControlKitRunner"
80 + ReferencedContainer = "container:XCRSControlKitRunner.xcodeproj">
81 + </BuildableReference>
82 + </BuildableProductRunnable>
83 + </ProfileAction>
84 + <AnalyzeAction
85 + buildConfiguration = "Debug">
86 + </AnalyzeAction>
87 + <ArchiveAction
88 + buildConfiguration = "Release"
89 + revealArchiveInOrganizer = "YES">
90 + </ArchiveAction>
91 +</Scheme>
Sources/XCRSControlKit/XCRSControlKit.docc/XCRSControlKit.md new
+12
@@ -0,0 +1,12 @@
1 +# ``XCRSControlKit``
2 +
3 +Shared contracts for black-box automation of Apple platforms.
4 +
5 +## Overview
6 +
7 +XCRSControlKit keeps platform runner contracts independent from the Xcode
8 +projects that execute XCTest and XCUITest automation. Consumers can discover a
9 +runner's platform and capabilities, identify a target, and send transport-neutral
10 +requests without depending on a particular runner implementation.
11 +
12 +The package does not import XCTest or private Apple frameworks.
Sources/XCRSControlKit/XCRSControlKit.swift new
+137
@@ -0,0 +1,137 @@
1 +import Foundation
2 +
3 +/// The Apple platform represented by a ControlKit runner.
4 +public enum XCRSControlKitPlatform: String, Codable, CaseIterable, Sendable {
5 + case iOS
6 + case iPadOS
7 + case tvOS
8 + case watchOS
9 + case visionOS
10 + case macOS
11 +}
12 +
13 +/// An operation supported by a ControlKit runner.
14 +public enum XCRSControlKitCapability: String, Codable, CaseIterable, Sendable {
15 + case accessibility
16 + case screenshot
17 + case appLifecycle
18 + case touch
19 + case textInput
20 + case swipe
21 + case orientation
22 + case remoteButtons
23 + case keyboard
24 + case pointer
25 + case spatialInteraction
26 + case pairedDevice
27 +}
28 +
29 +/// A simulator or device controlled by a runner.
30 +public struct XCRSControlKitTarget: Codable, Equatable, Sendable {
31 + public let platform: XCRSControlKitPlatform
32 + public let identifier: String
33 + public let name: String?
34 +
35 + public init(
36 + platform: XCRSControlKitPlatform,
37 + identifier: String,
38 + name: String? = nil
39 + ) {
40 + self.platform = platform
41 + self.identifier = identifier
42 + self.name = name
43 + }
44 +}
45 +
46 +/// The capabilities advertised by a runner.
47 +public struct XCRSControlKitCapabilities: Codable, Equatable, Sendable {
48 + public let platform: XCRSControlKitPlatform
49 + public let values: Set<XCRSControlKitCapability>
50 +
51 + public init(
52 + platform: XCRSControlKitPlatform,
53 + values: Set<XCRSControlKitCapability>
54 + ) {
55 + self.platform = platform
56 + self.values = values
57 + }
58 +
59 + public func supports(_ capability: XCRSControlKitCapability) -> Bool {
60 + values.contains(capability)
61 + }
62 +
63 + public static func standard(
64 + for platform: XCRSControlKitPlatform
65 + ) -> Self {
66 + let values: Set<XCRSControlKitCapability>
67 +
68 + switch platform {
69 + case .iOS, .iPadOS:
70 + values = [
71 + .accessibility,
72 + .screenshot,
73 + .appLifecycle,
74 + .touch,
75 + .textInput,
76 + .swipe,
77 + .orientation
78 + ]
79 + case .tvOS:
80 + values = [
81 + .accessibility,
82 + .screenshot,
83 + .appLifecycle,
84 + .remoteButtons
85 + ]
86 + case .watchOS:
87 + values = [
88 + .accessibility,
89 + .screenshot,
90 + .appLifecycle,
91 + .pairedDevice
92 + ]
93 + case .visionOS:
94 + values = [
95 + .accessibility,
96 + .screenshot,
97 + .appLifecycle,
98 + .spatialInteraction
99 + ]
100 + case .macOS:
101 + values = [
102 + .accessibility,
103 + .screenshot,
104 + .appLifecycle,
105 + .keyboard,
106 + .pointer
107 + ]
108 + }
109 +
110 + return Self(platform: platform, values: values)
111 + }
112 +}
113 +
114 +/// A transport-neutral command sent to a runner.
115 +public struct XCRSControlKitRequest: Codable, Equatable, Sendable {
116 + public let id: String
117 + public let method: String
118 + public let parameters: [String: String]
119 +
120 + public init(
121 + id: String = UUID().uuidString,
122 + method: String,
123 + parameters: [String: String] = [:]
124 + ) {
125 + self.id = id
126 + self.method = method
127 + self.parameters = parameters
128 + }
129 +}
130 +
131 +/// Errors shared by package consumers and platform runners.
132 +public enum XCRSControlKitError: Error, Equatable, Sendable {
133 + case unsupportedPlatform(XCRSControlKitPlatform)
134 + case unsupportedCapability(XCRSControlKitCapability)
135 + case invalidRequest(String)
136 + case transport(String)
137 +}
Tests/XCRSControlKitTests/XCRSControlKitTests.swift new
+44
@@ -0,0 +1,44 @@
1 +import XCTest
2 +@testable import XCRSControlKit
3 +
4 +final class XCRSControlKitTests: XCTestCase {
5 + func testStandardCapabilitiesArePlatformSpecific() {
6 + let iOSCapabilities = XCRSControlKitCapabilities.standard(for: .iOS)
7 + let tvOSCapabilities = XCRSControlKitCapabilities.standard(for: .tvOS)
8 +
9 + XCTAssertTrue(iOSCapabilities.supports(.touch))
10 + XCTAssertFalse(iOSCapabilities.supports(.remoteButtons))
11 + XCTAssertTrue(tvOSCapabilities.supports(.remoteButtons))
12 + XCTAssertFalse(tvOSCapabilities.supports(.touch))
13 + }
14 +
15 + func testCapabilitiesAreCodable() throws {
16 + let capabilities = XCRSControlKitCapabilities.standard(for: .macOS)
17 + let encoded = try JSONEncoder().encode(capabilities)
18 + let decoded = try JSONDecoder().decode(
19 + XCRSControlKitCapabilities.self,
20 + from: encoded
21 + )
22 +
23 + XCTAssertEqual(decoded, capabilities)
24 + }
25 +
26 + func testRequestHasStableTransportShape() throws {
27 + let request = XCRSControlKitRequest(
28 + id: "request-1",
29 + method: "device.io.button",
30 + parameters: ["button": "select"]
31 + )
32 +
33 + let object = try JSONSerialization.jsonObject(
34 + with: JSONEncoder().encode(request)
35 + ) as? [String: Any]
36 +
37 + XCTAssertEqual(object?["id"] as? String, "request-1")
38 + XCTAssertEqual(object?["method"] as? String, "device.io.button")
39 + XCTAssertEqual(
40 + (object?["parameters"] as? [String: String])?["button"],
41 + "select"
42 + )
43 + }
44 +}