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
CloudNativePGを布教したい
nnaka2992
0
100
CDK開発におけるコーディング規約の運用
yamanashi_ren01
2
190
Open source software: how to live long and go far
gaelvaroquaux
0
650
苦しいTiDBへの移行を乗り越えて快適な運用を目指す
leveragestech
0
820
パスキーのすべて ── 導入・UX設計・実装の紹介 / 20250213 パスキー開発者の集い
kuralab
3
850
Honoのおもしろいミドルウェアをみてみよう
yusukebe
1
210
第3回関東Kaggler会_AtCoderはKaggleの役に立つ
chettub
3
1.1k
負債になりにくいCSSをデザイナとつくるには?
fsubal
10
2.5k
Serverless Rust: Your Low-Risk Entry Point to Rust in Production (and the benefits are huge)
lmammino
1
140
Ruby on cygwin 2025-02
fd0
0
150
Multi Step Form, Decentralized Autonomous Organization
pumpkiinbell
1
810
JavaScriptツール群「UnJS」を5分で一気に駆け巡る!
k1tikurisu
9
1.8k
Featured
See All Featured
Practical Orchestrator
shlominoach
186
10k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
27
1.6k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
46
2.3k
Building a Scalable Design System with Sketch
lauravandoore
461
33k
The Pragmatic Product Professional
lauravandoore
32
6.4k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
49
2.3k
Automating Front-end Workflow
addyosmani
1368
200k
Designing for Performance
lara
604
68k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
330
21k
KATA
mclloyd
29
14k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
100
18k
Facilitating Awesome Meetings
lara
52
6.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 䱰⾂ ؽ٨ضٌ ✌猳