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
960
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
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
350
How did I build an Open-Source SwiftUI Toast Library
lovee
1
71
SwiftUIで使いやすいToastの作り方 / How to build a Toast system which is easy to use in SwiftUI
lovee
3
810
SwiftUIで二重スクロール作ってみた / When I tried to make a dual-scroll-ish view in SwiftUI
lovee
1
280
Observation のあれこれ / A brief introduction about Observation
lovee
3
350
ChatGPT 時代の勉強 / Learning under ChatGPT era
lovee
27
8.6k
属人化しない為の勉強会作り / To make tech meetups with less personal dependencies
lovee
0
300
偏見と妄想で語るスクリプト言語としての Swift / Swift as a Scripting Language
lovee
2
830
danger-swift-kantoku
lovee
1
520
Other Decks in Programming
See All in Programming
責務と認知負荷を整える! 抽象レベルを意識した関心の分離
yahiru
8
1.1k
密集、ドキュメントのコロケーション with AWS Lambda
satoshi256kbyte
1
200
負債になりにくいCSSをデザイナとつくるには?
fsubal
10
2.5k
バックエンドのためのアプリ内課金入門 (サブスク編)
qnighy
8
1.8k
なぜイベント駆動が必要なのか - CQRS/ESで解く複雑系システムの課題 -
j5ik2o
12
4.4k
さいきょうのレイヤードアーキテクチャについて考えてみた
yahiru
3
760
Open source software: how to live long and go far
gaelvaroquaux
0
650
sappoRo.R #12 初心者セッション
kosugitti
0
260
第3回関東Kaggler会_AtCoderはKaggleの役に立つ
chettub
3
1.1k
一休.com のログイン体験を支える技術 〜Web Components x Vue.js 活用事例と最適化について〜
atsumim
0
660
パスキーのすべて ── 導入・UX設計・実装の紹介 / 20250213 パスキー開発者の集い
kuralab
3
830
Ruby on cygwin 2025-02
fd0
0
150
Featured
See All Featured
Understanding Cognitive Biases in Performance Measurement
bluesmoon
27
1.6k
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.3k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Stop Working from a Prison Cell
hatefulcrawdad
267
20k
Measuring & Analyzing Core Web Vitals
bluesmoon
6
250
Adopting Sorbet at Scale
ufuk
74
9.2k
What's in a price? How to price your products and services
michaelherold
244
12k
Navigating Team Friction
lara
183
15k
StorybookのUI Testing Handbookを読んだ
zakiyama
28
5.5k
Scaling GitHub
holman
459
140k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
RailsConf 2023
tenderlove
29
1k
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 䱰⾂ ؽ٨ضٌ ✌猳