Skip to content

Commit b38c3df

Browse files
committed
refactor(demo): stabilize Appium accessibility IDs
1 parent 6eb5259 commit b38c3df

11 files changed

Lines changed: 184 additions & 166 deletions

examples/demo/App/Models/AppModels.swift

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,6 @@ enum InAppMessageType: String, CaseIterable, Identifiable {
6868
case .fullScreen: return "full_screen"
6969
}
7070
}
71-
72-
var iconName: String {
73-
switch self {
74-
case .topBanner: return "arrow.up.to.line"
75-
case .bottomBanner: return "arrow.down.to.line"
76-
case .centerModal: return "square"
77-
case .fullScreen: return "arrow.up.left.and.arrow.down.right"
78-
}
79-
}
8071
}
8172

8273
// MARK: - Add Item Type
@@ -152,6 +143,42 @@ enum AddItemType {
152143
case .externalUserId: return "login_user_id"
153144
}
154145
}
146+
147+
/// Accessibility id for the first text field in two-input dialogs.
148+
/// Mirrors the shared Appium spec naming (`alias_label_input`,
149+
/// `tag_key_input`, `trigger_key_input`).
150+
var keyInputID: String {
151+
switch self {
152+
case .alias: return "alias_label_input"
153+
case .tag: return "tag_key_input"
154+
case .trigger: return "trigger_key_input"
155+
default: return "\(accessibilityKey)_key_input"
156+
}
157+
}
158+
159+
/// Accessibility id for the second / single text field.
160+
/// Mirrors the shared Appium spec naming (`alias_id_input`,
161+
/// `tag_value_input`, `trigger_value_input`, `email_input`,
162+
/// `sms_input`, `login_user_id_input`).
163+
var valueInputID: String {
164+
switch self {
165+
case .alias: return "alias_id_input"
166+
case .tag: return "tag_value_input"
167+
case .trigger: return "trigger_value_input"
168+
default: return "\(accessibilityKey)_input"
169+
}
170+
}
171+
172+
/// Two-input flavors share `singlepair_*` buttons; single-input flavors
173+
/// share `singleinput_*` so the Appium suite can find them by a stable id
174+
/// regardless of the specific item type.
175+
var confirmButtonID: String {
176+
requiresKeyValue ? "singlepair_confirm_button" : "singleinput_confirm_button"
177+
}
178+
179+
var cancelButtonID: String {
180+
requiresKeyValue ? "singlepair_cancel_button" : "singleinput_cancel_button"
181+
}
155182
}
156183

157184
// MARK: - Multi-Add Item Type

examples/demo/App/ViewModels/OneSignalViewModel.swift

Lines changed: 7 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,12 @@ final class OneSignalViewModel: ObservableObject {
167167
service.consentGiven = true
168168
UserDefaults.standard.set(true, forKey: "CachedPrivacyConsent")
169169
}
170-
showToast(required ? "Consent required enabled" : "Consent required disabled")
171170
}
172171

173172
func setConsentGiven(_ granted: Bool) {
174173
consentGiven = granted
175174
service.consentGiven = granted
176175
UserDefaults.standard.set(granted, forKey: "CachedPrivacyConsent")
177-
showToast(granted ? "Consent given" : "Consent revoked")
178176
}
179177

180178
// MARK: - User
@@ -186,14 +184,12 @@ final class OneSignalViewModel: ObservableObject {
186184
service.login(externalId: trimmed)
187185
externalUserId = trimmed
188186
clearUserData()
189-
showToast("Logged in as \(trimmed)")
190187
}
191188

192189
func logout() {
193190
service.logout()
194191
externalUserId = nil
195192
clearUserData()
196-
showToast("Logged out")
197193
}
198194

199195
private func clearUserData() {
@@ -210,7 +206,6 @@ final class OneSignalViewModel: ObservableObject {
210206
service.addAlias(label: label, id: id)
211207
aliases.removeAll { $0.key == label }
212208
aliases.append(KeyValueItem(key: label, value: id))
213-
showToast("Alias added")
214209
}
215210

216211
func addAliases(_ pairs: [(String, String)]) {
@@ -220,13 +215,11 @@ final class OneSignalViewModel: ObservableObject {
220215
aliases.removeAll { $0.key == key }
221216
aliases.append(KeyValueItem(key: key, value: value))
222217
}
223-
showToast("\(pairs.count) alias(es) added")
224218
}
225219

226220
func removeAlias(_ item: KeyValueItem) {
227221
service.removeAlias(item.key)
228222
aliases.removeAll { $0.id == item.id }
229-
showToast("Alias removed")
230223
}
231224

232225
// MARK: - Push
@@ -235,11 +228,9 @@ final class OneSignalViewModel: ObservableObject {
235228
if enabled {
236229
service.optInPush()
237230
isPushEnabled = true
238-
showToast("Push enabled")
239231
} else {
240232
service.optOutPush()
241233
isPushEnabled = false
242-
showToast("Push disabled")
243234
}
244235
}
245236

@@ -248,7 +239,6 @@ final class OneSignalViewModel: ObservableObject {
248239
Task { @MainActor in
249240
self?.hasNotificationPermission = accepted
250241
self?.isPushEnabled = accepted
251-
self?.showToast(accepted ? "Push permission granted" : "Push permission denied")
252242
}
253243
}
254244
}
@@ -258,27 +248,23 @@ final class OneSignalViewModel: ObservableObject {
258248
func addEmail(_ email: String) {
259249
service.addEmail(email)
260250
if !emails.contains(email) { emails.append(email) }
261-
showToast("Email added")
262251
}
263252

264253
func removeEmail(_ email: String) {
265254
service.removeEmail(email)
266255
emails.removeAll { $0 == email }
267-
showToast("Email removed")
268256
}
269257

270258
// MARK: - SMS
271259

272260
func addSms(_ number: String) {
273261
service.addSms(number)
274262
if !smsNumbers.contains(number) { smsNumbers.append(number) }
275-
showToast("SMS added")
276263
}
277264

278265
func removeSms(_ number: String) {
279266
service.removeSms(number)
280267
smsNumbers.removeAll { $0 == number }
281-
showToast("SMS removed")
282268
}
283269

284270
// MARK: - Tags
@@ -287,7 +273,6 @@ final class OneSignalViewModel: ObservableObject {
287273
service.addTag(key: key, value: value)
288274
tags.removeAll { $0.key == key }
289275
tags.append(KeyValueItem(key: key, value: value))
290-
showToast("Tag added")
291276
}
292277

293278
func addTags(_ pairs: [(String, String)]) {
@@ -297,20 +282,17 @@ final class OneSignalViewModel: ObservableObject {
297282
tags.removeAll { $0.key == key }
298283
tags.append(KeyValueItem(key: key, value: value))
299284
}
300-
showToast("\(pairs.count) tag(s) added")
301285
}
302286

303287
func removeTag(_ item: KeyValueItem) {
304288
service.removeTag(item.key)
305289
tags.removeAll { $0.id == item.id }
306-
showToast("Tag removed")
307290
}
308291

309292
func removeSelectedTags(_ keys: [String]) {
310293
guard !keys.isEmpty else { return }
311294
service.removeTags(keys)
312295
tags.removeAll { keys.contains($0.key) }
313-
showToast("\(keys.count) tag(s) removed")
314296
}
315297

316298
// MARK: - Outcomes
@@ -335,14 +317,12 @@ final class OneSignalViewModel: ObservableObject {
335317
func setIamPaused(_ paused: Bool) {
336318
isInAppMessagesPaused = paused
337319
service.isInAppMessagesPaused = paused
338-
showToast(paused ? "In-app messages paused" : "In-app messages resumed")
339320
}
340321

341322
func sendIamTrigger(_ type: InAppMessageType) {
342323
service.addTrigger(key: "iam_type", value: type.triggerValue)
343324
triggers.removeAll { $0.key == "iam_type" }
344325
triggers.append(KeyValueItem(key: "iam_type", value: type.triggerValue))
345-
showToast("Sent IAM trigger: \(type.rawValue)")
346326
}
347327

348328
// MARK: - Triggers
@@ -351,7 +331,6 @@ final class OneSignalViewModel: ObservableObject {
351331
service.addTrigger(key: key, value: value)
352332
triggers.removeAll { $0.key == key }
353333
triggers.append(KeyValueItem(key: key, value: value))
354-
showToast("Trigger added")
355334
}
356335

357336
func addTriggers(_ pairs: [(String, String)]) {
@@ -361,26 +340,22 @@ final class OneSignalViewModel: ObservableObject {
361340
triggers.removeAll { $0.key == key }
362341
triggers.append(KeyValueItem(key: key, value: value))
363342
}
364-
showToast("\(pairs.count) trigger(s) added")
365343
}
366344

367345
func removeTrigger(_ item: KeyValueItem) {
368346
service.removeTrigger(item.key)
369347
triggers.removeAll { $0.id == item.id }
370-
showToast("Trigger removed")
371348
}
372349

373350
func removeSelectedTriggers(_ keys: [String]) {
374351
guard !keys.isEmpty else { return }
375352
service.removeTriggers(keys)
376353
triggers.removeAll { keys.contains($0.key) }
377-
showToast("\(keys.count) trigger(s) removed")
378354
}
379355

380356
func clearTriggers() {
381357
service.clearTriggers()
382358
triggers.removeAll()
383-
showToast("All triggers cleared")
384359
}
385360

386361
// MARK: - Custom Events
@@ -395,12 +370,10 @@ final class OneSignalViewModel: ObservableObject {
395370
func setLocationShared(_ shared: Bool) {
396371
isLocationShared = shared
397372
service.isLocationShared = shared
398-
showToast(shared ? "Location sharing enabled" : "Location sharing disabled")
399373
}
400374

401375
func promptLocation() {
402376
service.requestLocationPermission()
403-
showToast("Location permission requested")
404377
}
405378

406379
func checkLocationShared() {
@@ -412,92 +385,52 @@ final class OneSignalViewModel: ObservableObject {
412385

413386
func clearAllNotifications() {
414387
service.clearAllNotifications()
415-
showToast("All notifications cleared")
416388
}
417389

418390
func sendNotification(_ type: NotificationType) {
419-
guard let subscriptionId = service.pushSubscriptionId, !subscriptionId.isEmpty else {
420-
showToast("No push subscription")
421-
return
422-
}
423-
showToast("Sending \(type.rawValue) notification...")
424-
NotificationSender.shared.sendNotification(type, appId: appId, subscriptionId: subscriptionId) { [weak self] result in
425-
Task { @MainActor in
426-
switch result {
427-
case .success:
428-
self?.showToast("\(type.rawValue) sent!")
429-
case .failure(let error):
430-
self?.showToast("Send failed: \(error.localizedDescription)")
431-
}
432-
}
433-
}
391+
guard let subscriptionId = service.pushSubscriptionId, !subscriptionId.isEmpty else { return }
392+
NotificationSender.shared.sendNotification(type, appId: appId, subscriptionId: subscriptionId) { _ in }
434393
}
435394

436395
func sendCustomNotification(title: String, body: String) {
437-
guard let subscriptionId = service.pushSubscriptionId, !subscriptionId.isEmpty else {
438-
showToast("No push subscription")
439-
return
440-
}
441-
NotificationSender.shared.sendCustomNotification(title: title, body: body, appId: appId, subscriptionId: subscriptionId) { [weak self] result in
442-
Task { @MainActor in
443-
switch result {
444-
case .success:
445-
self?.showToast("Custom notification sent")
446-
case .failure(let error):
447-
self?.showToast("Send failed: \(error.localizedDescription)")
448-
}
449-
}
450-
}
396+
guard let subscriptionId = service.pushSubscriptionId, !subscriptionId.isEmpty else { return }
397+
NotificationSender.shared.sendCustomNotification(title: title, body: body, appId: appId, subscriptionId: subscriptionId) { _ in }
451398
}
452399

453400
// MARK: - Live Activities
454401

455402
func startLiveActivity(activityId: String, orderNumber: String, status: LiveActivityStatus) {
456403
let trimmedId = activityId.trimmingCharacters(in: .whitespacesAndNewlines)
457-
guard !trimmedId.isEmpty else {
458-
showToast("Activity ID required")
459-
return
460-
}
404+
guard !trimmedId.isEmpty else { return }
461405
if #available(iOS 16.1, *) {
462406
LiveActivityController.start(
463407
activityId: trimmedId,
464408
orderNumber: orderNumber,
465409
status: status
466410
)
467-
showToast("Live Activity '\(trimmedId)' started")
468-
} else {
469-
showToast("Live Activities require iOS 16.1+")
470411
}
471412
}
472413

473414
func updateLiveActivity(activityId: String, status: LiveActivityStatus) {
474415
let trimmedId = activityId.trimmingCharacters(in: .whitespacesAndNewlines)
475416
guard !trimmedId.isEmpty else { return }
476-
showToast("Updating Live Activity...")
477417
Task {
478-
let success = await LiveActivityController.update(
418+
_ = await LiveActivityController.update(
479419
appId: appId,
480420
activityId: trimmedId,
481421
status: status
482422
)
483-
await MainActor.run {
484-
showToast(success ? "Live Activity updated" : "Update failed")
485-
}
486423
}
487424
}
488425

489426
func endLiveActivity(activityId: String) {
490427
let trimmedId = activityId.trimmingCharacters(in: .whitespacesAndNewlines)
491428
guard !trimmedId.isEmpty else { return }
492-
showToast("Ending Live Activity...")
493429
Task {
494-
let success = await LiveActivityController.end(
430+
_ = await LiveActivityController.end(
495431
appId: appId,
496432
activityId: trimmedId
497433
)
498-
await MainActor.run {
499-
showToast(success ? "Live Activity ended" : "End failed")
500-
}
501434
}
502435
}
503436

examples/demo/App/Views/Components/AddItemDialog.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ struct AddItemDialog: View {
4141
title: itemType.title,
4242
confirmLabel: itemType.confirmLabel,
4343
isConfirmEnabled: isValid,
44-
confirmAccessibilityID: "singleinput_confirm_button",
45-
cancelAccessibilityID: "singleinput_cancel_button",
44+
confirmAccessibilityID: itemType.confirmButtonID,
45+
cancelAccessibilityID: itemType.cancelButtonID,
4646
onConfirm: {
4747
onAdd(
4848
keyText.trimmingCharacters(in: .whitespaces),
@@ -56,20 +56,20 @@ struct AddItemDialog: View {
5656
OSTextField(
5757
placeholder: itemType.keyPlaceholder,
5858
text: $keyText,
59-
accessibilityID: "\(itemType.accessibilityKey)_key_input"
59+
accessibilityID: itemType.keyInputID
6060
)
6161
OSTextField(
6262
placeholder: itemType.valuePlaceholder,
6363
text: $valueText,
6464
keyboardType: itemType.keyboardType,
65-
accessibilityID: "\(itemType.accessibilityKey)_value_input"
65+
accessibilityID: itemType.valueInputID
6666
)
6767
} else {
6868
OSTextField(
6969
placeholder: itemType.valuePlaceholder,
7070
text: $valueText,
7171
keyboardType: itemType.keyboardType,
72-
accessibilityID: "\(itemType.accessibilityKey)_input"
72+
accessibilityID: itemType.valueInputID
7373
)
7474
}
7575
}

0 commit comments

Comments
 (0)