Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
How to stabilize UI tests using XCTest
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Akio Itaya
March 10, 2026
Programming
180
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
How to stabilize UI tests using XCTest
Ebisu.mobile #13 〜STORESのモバイルテストの現在地〜
https://hey.connpass.com/event/382942/
Akio Itaya
March 10, 2026
More Decks by Akio Itaya
See All by Akio Itaya
What If Your App Could Debug Itself?
akkeylab
0
41
Bringing Spatial Web to E-Commerce
akkeylab
0
94
Learn CPU architecture with Assembly
akkeylab
1
2.1k
Porting a visionOS App to Android XR
akkeylab
0
1.2k
How to handle 3D content on Android XR
akkeylab
0
240
Create a website using Spatial Web
akkeylab
0
430
How to build visionOS apps using Windows
akkeylab
0
220
How to build visionOS apps using Persona
akkeylab
1
490
Summary - Introducing enterprise APls for visionOS
akkeylab
0
580
Other Decks in Programming
See All in Programming
決定論的オーケストレーションの設計と実装 / Design and Implementation of Deterministic Orchestration
nrslib
4
1.5k
さぁV100、メモリをお食べ・・・
nilpe
0
160
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
410
OSもどきOS
arkw
0
600
ふつうのFeature Flag実践入門
irof
8
4.2k
スマートグラスで並列バイブコーディング
hyshu
0
260
セキュリティの専門家じゃなくてもできる。「セキュリティ意識」をアップデートして サプライチェーン攻撃への耐性を高めよう。
tk3fftk
5
980
SREは、MCPとSRE Agentをこう使え!
kazumax55
0
120
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
810
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
7.1k
Snowflake Summitでの新機能 CoCo / CoWork / snowflake-summit-2026-overall-what-new-coco
tatsuhiro
1
190
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
640
Featured
See All Featured
XXLCSS - How to scale CSS and keep your sanity
sugarenia
250
1.3M
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
1
360
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.2k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
640
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.3k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
200
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Agile that works and the tools we love
rasmusluckow
331
22k
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
160
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.1k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Transcript
XCTestΛͬͨUIςετͷ҆ఆԽઓུ akkey / @AkkeyLab Ebisu.mobile #13
The Point 2 Wait & Scroll ࣮ߦڥͱίϯςϯπʹґଘ͠ͳ͍ςετΛࢦͯ͠
Wait
Wait 4 let app = XCUIApplication() let button = app.buttons.firstMatch
button.tap()
Wait 5 let app = XCUIApplication() let button = app.buttons.firstMatch
button.tap() ❌
Wait 6 let app = XCUIApplication() let button = app.buttons.firstMatch
button.tap() ❌ ❌
Wait 7 let app = XCUIApplication() let button = app.buttons.firstMatch
button.tap() ✅
Fix
Wait 9 let app = XCUIApplication() let button = app.buttons.firstMatch
let predicate = NSPredicate( format: "exists == true && hittable == true" ) let ex = XCTNSPredicateExpectation( predicate: predicate, object: button ) XCTWaiter.wait( for: [ex], timeout: 10 ) button.tap() ⏰ wait…
Wait 10 let app = XCUIApplication() let button = app.buttons.firstMatch
let predicate = NSPredicate( format: "exists == true && hittable == true" ) let ex = XCTNSPredicateExpectation( predicate: predicate, object: button ) XCTWaiter.wait( for: [ex], timeout: 10 ) button.tap() ⏰ wait…
Wait 11 let app = XCUIApplication() let button = app.buttons.firstMatch
let predicate = NSPredicate( format: "exists == true && hittable == true" ) let ex = XCTNSPredicateExpectation( predicate: predicate, object: button ) XCTWaiter.wait( for: [ex], timeout: 10 ) button.tap() ✅ completed! ✅
Wait 12 let predicate = NSPredicate { object, _ in
guard let element = object as? XCUIElement else { return false } return element.exists && element.isHittable } let ex = XCTNSPredicateExpectation( predicate: predicate, object: button ) XCTWaiter.wait( for: [ex], timeout: 10 ) button.tap() ✅ completed! ✅
Scroll
Scroll 14
Scroll 15
Scroll 16
Scroll 17 scrollView.swipeLeft(velocity: .slow)
Scroll 18 scrollView.swipeLeft(velocity: .slow) ⚠
Fix
let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) let end
= start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) Scroll 20 ☝
Scroll 21 let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) 350 ☝
Scroll 22 let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) ☝ 350 -350
Scroll 23 let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) 350 ☝
Scroll 24 let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) 350 ☝ ⏰0.02s
Scroll 25 let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) 350 ☝
Scroll 26 let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) 350 ☝ 🚀250px/s -350
Scroll 27 let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) 350 ☝ ⏰0.07s
Scroll 28 let start = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = start.withOffset(CGVector(dx: -350, dy: 0)) start.press( forDuration: 0.02, thenDragTo: end, withVelocity: 250, thenHoldForDuration: 0.07 ) 350 ⏰0.07s ☝
The Point 29 Wait & Scroll ࣮ߦڥͱίϯςϯπʹґଘ͠ͳ͍ςετΛࢦͯ͠
Thank you !!