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
Share Extension の UI Test / UI Test for Share E...
Search
Elvis Shi
January 24, 2020
Programming
1
980
Share Extension の UI Test / UI Test for Share Extension
Share Extension を対応したとき、共有メニューの UI テストをどうすればいいのか
Elvis Shi
January 24, 2020
Tweet
Share
More Decks by Elvis Shi
See All by Elvis Shi
@Environment(\.keyPath)那么好我不允许你们不知道! / atEnvironment keyPath is so good and you should know it!
lovee
0
130
ゼロから始めるPreferenceの実装 / Let's implement Preferences from scratch
lovee
0
89
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
300
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
520
How did I build an Open-Source SwiftUI Toast Library
lovee
1
120
SwiftUIで使いやすいToastの作り方 / How to build a Toast system which is easy to use in SwiftUI
lovee
3
1k
SwiftUIで二重スクロール作ってみた / When I tried to make a dual-scroll-ish view in SwiftUI
lovee
1
320
Observation のあれこれ / A brief introduction about Observation
lovee
3
390
ChatGPT 時代の勉強 / Learning under ChatGPT era
lovee
27
8.8k
Other Decks in Programming
See All in Programming
Zendeskのチケットを Amazon Bedrockで 解析した
ryokosuge
3
320
Design Foundational Data Engineering Observability
sucitw
3
200
1から理解するWeb Push
dora1998
7
1.9k
Amazon RDS 向けに提供されている MCP Server と仕組みを調べてみた/jawsug-okayama-2025-aurora-mcp
takahashiikki
1
120
AI Coding Agentのセキュリティリスク:PRの自己承認とメルカリの対策
s3h
0
240
パッケージ設計の黒魔術/Kyoto.go#63
lufia
3
440
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
420
テストコードはもう書かない:JetBrains AI Assistantに委ねる非同期処理のテスト自動設計・生成
makun
0
540
デザイナーが Androidエンジニアに 挑戦してみた
874wokiite
0
550
Improving my own Ruby thereafter
sisshiki1969
1
160
知っているようで知らない"rails new"の世界 / The World of "rails new" You Think You Know but Don't
luccafort
PRO
1
190
Reading Rails 1.0 Source Code
okuramasafumi
0
250
Featured
See All Featured
Documentation Writing (for coders)
carmenintech
74
5k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
8
930
jQuery: Nuts, Bolts and Bling
dougneiner
64
7.9k
A designer walks into a library…
pauljervisheath
207
24k
Stop Working from a Prison Cell
hatefulcrawdad
271
21k
Designing Experiences People Love
moore
142
24k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
How to Ace a Technical Interview
jacobian
279
23k
What's in a price? How to price your products and services
michaelherold
246
12k
For a Future-Friendly Web
brad_frost
180
9.9k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
252
21k
RailsConf 2023
tenderlove
30
1.2k
Transcript
4IBSF&YUFOTJPOͷ6*5FTU for )","5"5FTU/JHIU
} var employedBy = "YUMEMI Inc." var job = "iOS
Tech Lead" var favoriteLanguage = "Swift" var twitter = "@lovee" var qiita = "lovee" var github = "el-hoshino" var additionalInfo = """ ཏখࠇઓهʢϩγϟΦϔΠηϯΩʣөը؍Α͏ʂ """ final class Me: Developable, Talkable {
None
None
None
None
4IBSF&YUFOTJPOΛରԠͨ͠ɻ 6*5FTUΛ͍ͨ͠ɻ Ͳ͏͢Ε͍͍ʁ
func testQRCodeGenerationFromSafari() { let app = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari") app.launch() XCTContext.runActivity(named:
"Go to about:blank page") { _ -> Void in let urlBar = app.otherElements["topBrowserBar"] urlBar.tap() urlBar.typeText("about:blank") app.buttons["Go"].tap() } XCTContext.runActivity(named: "Call Share menu") { _ -> Void in let shareButton = app.buttons["Share"] shareButton.tap() } XCTContext.runActivity(named: "Open QuickshaRe") { _ -> Void in let shareList = app.otherElements["ActivityListView"] XCTAssert(shareList.waitForExistence(timeout: 2)) let cell = shareList.cells.matching(identifier: "Activity").allElementsBoundByIndex[1] cell.tap() } XCTContext.runActivity(named: "Check label and image display") { _ -> Void in let view = app.otherElements["Share View"] let label = view.staticTexts["about:blank"] let image = view.images["QR Image"] XCTAssert(view.waitForExistence(timeout: 2)) XCTAssert(label.exists) XCTAssert(image.exists) let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil)! let qrCIImage = CIImage(image: image.screenshot().image)! let features = detector.features(in: qrCIImage) as! [CIQRCodeFeature] XCTAssertEqual(features[0].messageString, "about:blank") } }
6*5FTUͱ6OJU5FTUͷҧ͍ w 6OJU5FTUɿϝιουͷॲཧʹର͢ΔϩδοΫͷςετ w ΞϓϦຊମΛ@testable import͠ɺϝιουΦϒδΣΫτ ͷςετରΛ࣮ࡍʹݺͼग़ͯ͠ಈ࡞Λ֬ೝ w ඞཁʹԠͯ͡ςετରʹςετ͚ͷґଘΛೖՄೳ w
ΞϓϦͷ࣮Λʢ͋ΔఔʣѲͨ͠ϗϫΠτϘοΫεςετ w 6*5FTUɿϏϧυࡁΈͷΞϓϦΛಈ͔͢ςετ w ΞϓϦຊମΛ@testable importͤͣʹΞϓϦΛૢ࡞ͯ͠ ಈ࡞Λ֬ೝ w ςετ͚ͷґଘೖ͍͠ w ΞϓϦͷ࣮Λશ͘Ѳ͠ͳ͍ϒϥοΫϘοΫεςετ
ς ε τ ର ͷ ຊ ମ Ξ
ϓ Ϧ ࣮ ࡍ ͷ ς ε τ λ ʔ ή ỽ τ ͔ Β Ϗ ϧ υ ͠ ͨ Ξ ϓ Ϧ
2VJDLTIB3F ։͚ ͍
4BGBSJ։͚ ͍
let app = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari") app.launch()
None
ΞϓϦ։͚ͨɻ Ͳ͏ૢ࡞͢Ε͍͍ͷʁ
.staticTexts["Some Text"] .textFields["Placeholder Text"] .buttons["Button Title"] .images["Accessibility Identifier"] .otherElements["Identifier"] .exists
.waitForExistence(timeout: 2) .tap() .typeText("Some Typing") // etc...
.staticTexts["Some Text"] .textFields["Placeholder Text"] .buttons["Button Title"] .images["Accessibility Identifier"] .otherElements["Identifier"] .exists
.waitForExistence(timeout: 2) .tap() .typeText("Some Typing") // etc... ͜ΕΒͷ"1*ΛΈ߹Θͤͯ ΞϓϦʹରͯ͠র߹ૢ࡞Λ༩͑Δ
ڞ༗ϝχϡʔରԠͷ6*5FTU֬ೝखॱ 4BGBSJΛࢦఆͷ63-ʹભҠ ಡΈࠐΈ͕ऴΘͬͨΒڞ༗ϝχϡʔΛ։͘ ڞ༗ϝχϡʔ͔Β֘ΞϓϦΛ։͘ ։͍ͨը໘͕ਖ਼͘͠දࣔͰ͖͍ͯΔ͔ΛνΣοΫ
εςοϓɿࢦఆͷ63-ʹભҠ ϒϥβόʔΛԡ͢ ϒϥβόʔʹࢦఆͷ63-Λೖྗ ΩʔϘʔυͷA(PAΩʔΛԡ͢
// ϒϥβը໘ͷ্ʹ͋ΔϒϥβόʔΛ୳͢ let urlBar = app.otherElements["topBrowserBar"] // ͦͷϒϥβόʔΛԡ͢ urlBar.tap() //
ϒϥβόʔʹࢦఆͷจࣈΛೖΕΔ urlBar.typeText("about:blank") // ΩʔϘʔυͷ `Go` ΩʔΛԡ͢ app.buttons["Go"].tap()
None
εςοϓɿڞ༗ϝχϡʔΛ։͘ ڞ༗ϘλϯΛԡ͢
// ΞϓϦͷڞ༗ϘλϯΛ୳͢ let shareButton = app.buttons["Share"] // ͦͷڞ༗ϘλϯΛԡ͢ shareButton.tap()
None
εςοϓɿ֘ΞϓϦΛ։͘ ڞ༗ϝχϡʔ͕දࣔ͞ΕΔ·Ͱͭ ڞ༗ϝχϡʔͷ֘ΞϓϦͷΞΠίϯΛԡ͢
// ڞ༗ϝχϡʔΛ୳͢ let shareList = app.otherElements["ActivityListView"] // ڞ༗ϝχϡʔ͕දࣔ͞ΕΔ·Ͱ࠷େ 2 ඵͭ
XCTAssert(shareList.waitForExistence(timeout: 2)) // ͦͷڞ༗ϝχϡʔ͔Β֘ΞϓϦͷΞΠίϯΛ୳͢ let cell = shareList.cells.matching(identifier: "Activity") .allElementsBoundByIndex[1] // ͦͷΞΠίϯΛԡ͢ cell.tap()
None
εςοϓɿਖ਼͘͠දࣔͰ͖͍ͯΔ͔ ࢦఆͷ63-ςΩετද͕ࣔ͋Δ 23ίʔυը૾ද͕ࣔ͋Δ 23ίʔυΛಡΈऔΕࢦఆͷ63-ςΩετͰ͋Δ
// Share View Λ୳͢ let view = app.otherElements["Share View"] //
ࢦఆͷςΩετදࣔΛ୳͢ let label = view.staticTexts["about:blank"] // QR ίʔυը૾දࣔΛ୳͢ let image = view.images["QR Image"] // Share View ͕දࣔ͞ΕΔ·Ͱ࠷େ 2 ඵͭ XCTAssert(view.waitForExistence(timeout: 2)) // ࢦఆͷ URL ςΩετද͕ࣔ͋Δ͜ͱΛ֬ೝ XCTAssert(label.exists) // QR ίʔυը૾ද͕ࣔ͋Δ͜ͱΛ֬ೝ XCTAssert(image.exists) // ͦͷը૾දࣔΛ QR ίʔυͱͯ͠ಡΈऔΕࢦఆͷ URL ςΩετͰ͋Δ͜ͱΛ֬ೝ let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil)! let qrCIImage = CIImage(image: image.screenshot().image)! let features = detector.features(in: qrCIImage) as! [CIQRCodeFeature] XCTAssertEqual(features[0].messageString, "about:blank") વͳ͕Β JNQPSU$PSF*NBHF ͕ඞཁ
None
// ڞ༗ϝχϡʔΛ୳͢ let shareList = app.otherElements["ActivityListView"] // ڞ༗ϝχϡʔ͕දࣔ͞ΕΔ·Ͱ࠷େ 2 ඵͭ
XCTAssert(shareList.waitForExistence(timeout: 2)) // ͦͷڞ༗ϝχϡʔ͔Β֘ΞϓϦͷΞΠίϯΛ୳͢ let cell = shareList.cells.matching(identifier: "Activity") .allElementsBoundByIndex[1] // ͦͷΞΠίϯΛԡ͢ cell.tap() ͳΜ͡Ό͜Εʂʁ NBUDIJOHʁ "DUJWJUZʁ <>ʁ
6*5FTUͰͲ͏ͬͯ ը໘ཁૉΛಛఆ͢Δͷ͔
// ࢦఆͷςΩετදࣔΛ୳͢ let label = view.staticTexts["about:blank"] ςΩετͳΒ
ςΩετͳΒ wͦͷ··["ݻఆςΩετ"]ͰׂΓग़͢ • staticTexts["Text"] • buttons["Button Title"] ˞ʹΑͬͯͳ͔ͥऔΕͳ͍ը໘ཁૉ͋Δ
// QR ίʔυը૾දࣔΛ୳͢ let image = view.images["QR Image"] ςΩετҎ֎ͰࣗͷΞϓϦͳΒ
ςΩετҎ֎ͰࣗͷΞϓϦͳΒ wBccessibilityIdentifierΛઃఆͯ͠ ["Accessibility Identifier"]͔ΒׂΓग़͢ • images["Accessibility Identifier"] • otherElements["Accessibility Identifier"]
XCTContext.runActivity(named: "Search Elements") { (activity) -> Void in // app
͔ΒݟΕΔͯ͢ͷ otherElements Λࠪ app.otherElements.allElementsBoundByIndex.forEach { (element) in // ඞͣ element ͕ଘࡏ͠ɺ͔ͭαΠζ͕ zero ͡Όͳ͍͜ͱΛอূ guard element.exists, element.frame.size != .zero else { return } // ֘ element ͷεΫϦʔϯγϣοτΛࡱΔ let screenshot = element.screenshot() // εΫϦʔϯγϣοτ͔ΒఴΛ࡞Δ let attachment = XCTAttachment(screenshot: screenshot) // ݱࡏͷ activity ʹఴΛՃ activity.add(attachment) } } ςΩετҎ֎ͰଞਓͷΞϓϦͳΒ
ςΩετҎ֎ͰଞਓͷΞϓϦͳΒ ֘ͦ͠͏ͳࢠཁૉͷछྨΛશ෦ࠪ͢Δ ͦΕΒͷཁૉΛͯ͢εΫγϣΛࡱͬͯϨϙʔτʹఴ͢Δ ϨϙʔτͷఴεΫγϣը૾͔Β֘͢ΔཁૉΛ୳͠ग़͠ɺ ఴ໊͔Βཁૉ໊Λಛఆ͢Δ
XCTContext.runActivity(named: "Search Elements") { activity -> Void in let shareList
= app.otherElements["ActivityListView"] XCTAssert(shareList.waitForExistence(timeout: 2)) // ڞ༗ϝχϡʔ ͔ΒݟΕΔͯ͢ͷ cell Λࠪ shareList.cells.allElementsBoundByIndex.forEach { (cell) in guard cell.exists, cell.frame.size != .zero else { return } let screenshot = cell.screenshot() let attachment = XCTAttachment(screenshot: screenshot) activity.add(attachment) } } ଞਓͷΞϓϦͳΒ
ݱஈ֊ɺڞ༗ϝχϡʔ͔Β ਖ਼֬ʹࣗͷΞϓϦϘλϯΛ ׂΓग़͢͜ͱෆՄೳ ˞ࢀߟɿIUUQTHJTUHJUIVCDPN"WE-FFCEFEGDDBDBEHJTUDPNNFOU
// ڞ༗ϝχϡʔΛ୳͢ let shareList = app.otherElements["ActivityListView"] // ڞ༗ϝχϡʔ͕දࣔ͞ΕΔ·Ͱ࠷େ 2 ඵͭ
XCTAssert(shareList.waitForExistence(timeout: 2)) // ͦͷڞ༗ϝχϡʔ͔Β֘ΞϓϦͷΞΠίϯΛ୳͢ let cell = shareList.cells.matching(identifier: "Activity") .allElementsBoundByIndex[1] // ͦͷΞΠίϯΛԡ͢ cell.tap() ͳΜ͡Ό͜Εʂʁ NBUDIJOHʁ "DUJWJUZʁ <>ʁ
// ͯ͢ͷηϧ͔Βࠪ let cell = shareList.cells // `Activity` ͷηϧΛநग़ .matching(identifier:
"Activity") // ͦͷ 2 ൪ͷηϧ .allElementsBoundByIndex[1] ˞࣮ߦ͞ΕΔͷڞ༗ઃఆʹґଘ͢ΔͨΊ$*Ͱอূͤ͞Δ͜ͱݱࡏෆՄೳ
·ͱΊ w 6*5FTUΞϓϦʹରͯ͠ૢ࡞Λߦ͏ςετ w 6*5FTUͰ4IBSF&YUFOTJPOରԠͷڞ༗ը໘ςετՄೳ w εΫγϣΛࡱͬͯ֬ೝ͢Δ͜ͱͰ6*5FTUͰ͑Δཁૉ໊͕Θ͔Δ w ڞ༗ϝχϡʔࣗମݱஈ֊ສશͰਖ਼֬ͳςετίʔυ͕ॻ͚ͳ͍ ࢀߟɿIUUQTHJUIVCDPNFMIPTIJOP2VJDLTIB3FCMPCDFGDGGEBEEDFCDFE2VJDLTIB3F6*5FTUT2VJDLTIB3F6*5FTUTTXJGU
ʲએʳ
2VJDLTIB3Fɺઈࢍ৴தʂ IUUQTBQQTBQQMFDPNKQBQQRVJDLTIBSFTIBSFWJBRSDPEFJE
גࣜձࣾΏΊΈɺΤϯδχΞઈࢍืूத IUUQSFDSVJUZVNFNJDPKQ 䱰⾂ ؽ٨ضٌ ✌猳