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
970
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
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
260
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
460
How did I build an Open-Source SwiftUI Toast Library
lovee
1
100
SwiftUIで使いやすいToastの作り方 / How to build a Toast system which is easy to use in SwiftUI
lovee
3
970
SwiftUIで二重スクロール作ってみた / When I tried to make a dual-scroll-ish view in SwiftUI
lovee
1
310
Observation のあれこれ / A brief introduction about Observation
lovee
3
390
ChatGPT 時代の勉強 / Learning under ChatGPT era
lovee
27
8.8k
属人化しない為の勉強会作り / To make tech meetups with less personal dependencies
lovee
0
320
偏見と妄想で語るスクリプト言語としての Swift / Swift as a Scripting Language
lovee
2
870
Other Decks in Programming
See All in Programming
プロダクト志向なエンジニアがもう一歩先の価値を目指すために意識したこと
nealle
0
120
AWS CDKの推しポイント 〜CloudFormationと比較してみた〜
akihisaikeda
3
320
生成AIコーディングとの向き合い方、AIと共創するという考え方 / How to deal with generative AI coding and the concept of co-creating with AI
seike460
PRO
1
340
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
49
32k
Azure AI Foundryではじめてのマルチエージェントワークフロー
seosoft
0
150
エンジニア向け採用ピッチ資料
inusan
0
180
XP, Testing and ninja testing
m_seki
3
220
ソフトウェア品質を数字で捉える技術。事業成長を支えるシステム品質の マネジメント
takuya542
0
280
AIコーディング道場勉強会#2 君(エンジニア)たちはどう生きるか
misakiotb
1
270
Deep Dive into ~/.claude/projects
hiragram
10
2.1k
Railsアプリケーションと パフォーマンスチューニング ー 秒間5万リクエストの モバイルオーダーシステムを支える事例 ー Rubyセミナー 大阪
falcon8823
4
1k
LINEヤフー データグループ紹介
lycorp_recruit_jp
0
1.6k
Featured
See All Featured
YesSQL, Process and Tooling at Scale
rocio
173
14k
Java REST API Framework Comparison - PWX 2021
mraible
31
8.7k
Large-scale JavaScript Application Architecture
addyosmani
512
110k
Speed Design
sergeychernyshev
32
1k
Art, The Web, and Tiny UX
lynnandtonic
299
21k
Statistics for Hackers
jakevdp
799
220k
GitHub's CSS Performance
jonrohan
1031
460k
Intergalactic Javascript Robots from Outer Space
tanoku
271
27k
Code Reviewing Like a Champion
maltzj
524
40k
Optimizing for Happiness
mojombo
379
70k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
357
30k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
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 䱰⾂ ؽ٨ضٌ ✌猳