Automate iOS screenshot with clean status bar and upload result to firebase hosting 🤖

Patompong Manprasatkul
Bug in the shell
Published in
4 min readAug 1, 2018

เชื่อว่าหลายๆท่านคงจำเป็นที่จะต้องจับภาพหน้าจอตัวอย่างของ application เพื่อที่จะนำไปอัพโหลดให้ iTunes Connect ถ้าหากจะต้องปล่อยฟีเจอร์ใหม่ทุกๆ 1–2 เดือน ก็คงจะไม่มีปัญหาถ้าจะทำใหม่ทุกครั้ง เพราะแต่ละครั้งคงจะใช้เวลาอย่างน้อย 1 ชม. สำหรับ(3–5 หน้า) เพียงขนาดหน้าจอเดียว และมีเพียงไม่กี่ภาษา(ไทย/English) แต่ถ้าหากมีจำนวนหน้าเพิ่มขึ้น หลายขนาดหน้าจอ หรือ ภาษาที่รองรับมากขึ้น หรือ ฟีเจอร์ใหม่ปล่อยบ่อยขึ้น เช่น 5 หน้าจอ 3 ขนาด 3 ภาษา ทุกๆ 2 สัปดาห์ คงจะเหนื่อยไม่น้อยที่จะต้องมาจับภาพหน้าจอ manual แบบนี้ ทุก 2 สัปดาห์ ต้องจับภาพหน้าจอ 45 ครั้ง (5*3*3) ซึ่งใช้เวลาประมาณ 2–3 ชั่วโมง ต่อครั้งในการปล่อย application

Solution

วันนี้จะมาแนะนำ Fastlane, UITest, SimulatorStatusMagic เพื่อทำ screenshot และอัพโหลดเพื่อส่งงานด้วย firebase hosting (ผมไม่ใช้ระบบอัพโหลดเข้า iTunes Connect อัตโนมัติ เพราะต้องให้เพื่อนร่วมงานตรวจสอบก่อนครับ) ถ้าไม่ต้องการ upload ก็ข้ามช่วง firebase ไปได้เลยครับ

Fastlane + UITest

fastlane เป็นเครื่องมือสารพัดประโยชน์สำหรับนักพัฒนา iOS และ Android application โดยเราจะพูดถึงแค่ส่วน fastlane snapshot ถ้าหากว่ายังไม่ได้ติดตั้ง fastlane ให้พิมพ์ตามนี้

# install fastlane
$ [sudo] gem install fastlane -NV
# init fastlane (choose custom lane)
$ fastlane init

เริ่มใช้ฟีเจอร์ snapshot

$ fastlane snapshot init

สร้าง UI Test สำหรับ snapshot (File >> New >> Target >> UI Test)

File >> New >> Target >> UI Test
Target setting

ลากไฟล์ fastlane/SnapshotHelper.swift ไปยัง Snapshot group ใน Xcode

สร้าง scheme Snapshot ตั้งค่าให้เป็น share scheme (Manage Schemes >> check box shared) และตั้งค่า Build (Test/Run)

New scheme
Snapshot scheme build setting

เปลี่ยนชื่อไฟล์ Snapshot.swift >> SnapshotTest.swift และแก้ไขไฟล์เพื่อเพิ่ม test สำหรับ Snapshot ในส่วนของ setUp จะถูกเรียกทุกครั้งก่อนที่รันเทส และ tearDown จะถูกเรียกทุกครั้งหลังที่รันเทสครับ และ ส่วนเทสของเราจะอยู่ที่ testSnapshotSearchFlow ส่วนที่ขาดไม่ได้ก็คือ snapshot(“NAME”) ซึ่งจะเป็นคำสั่งที่ให้เริ่มจับภาพหน้าจอ ในที่นี้ผมจะไม่พูดถึงการเขียน UITest ให้กดปุ่มหรือพิมพ์ นะครับ โดยจะต้องเขียนขั้นตอนต่างๆไว้ใน setupNAMEScreen() เพื่อให้สามารถกดปุ่มไปหน้าที่ต้องการ หรือ พิมพ์ในช่อง TextField ต่างๆ

class SnapshotTest: XCTestCase {
var app: XCUIApplication!

override func setUp() {
app = XCUIApplication()
setupSnapshot(app)
app.launch()
super.setUp()
}

override func tearDown() {
app = nil
super.tearDown()
}

func testSnapshotSearchFlow() {
setupHomeScreen()
snapshot("Home")
setupCarTypeScreen()
snapshot("CarType")
backToHomeFromCarTypeScreen()
setupSearchScreen()
snapshot("Search")
setupCarListScreen()
snapshot("CarList")
}
}

แก้ไขไฟล์ fastlane/Snapfileเพื่อตั้งค่า devices, languages, scheme(ตั้งหน้าที่ต้องการ ซึ่งต้องตรงกับที่ใช้ใน UI test ด้านบน) ซึ่งในที่นี้ผมเลือก 4 ขนาดหน้าจอ 1 ภาษา และ 4 หน้าจอ (Home, CarType, Search, CarList) ครับ

devices([
"iPhone SE",
"iPhone 8",
"iPhone 8 Plus",
"iPhone X"
])
languages([
"en-US"
])
scheme("Home")
scheme("CarType")
scheme("Search")
scheme("CarList")

แก้ไขไฟล์ fastlane/Fastfile โดยให้ใส่ไว้ภายใต้ platform :ios do ซึ่ง PROJECT-NAME.xcworkspace และ SNAPSHOT-SCHEME-NAME จะต้องเปลี่ยนตามชื่อไฟล์ และ scheme

  lane :screenshots do
capture_screenshots(workspace: "PROJECT-NAME.xcworkspace", scheme: "SNAPSHOT-SCHEME-NAME")
end

ทดลองคำสั่ง fastlane screenshots ก็จะได้ภาพ snapshot ตามที่เราต้องการ ใน directory fastlane/screenshots

Customize status bar

ถ้าต้องการให้ status bar มีเวลาที่คงที่และ ต้องการซ่อน icon ต่างๆที่ไม่จำเป็นออกไป สามารถใช้ SimulatorStatusMagic ช่วยได้ เริ่มต้นด้วยการติดตั้ง pod ซึ่งต้องระบุชื่อ scheme ที่จะใช้ตามนี้ครับ target ‘SCHEME-NAME’ do

target 'Snapshot' do 
pod 'SimulatorStatusMagic'
end

แก้ไขไฟล์ SnapshotTest.swift ในส่วนของ setUp tearDown และ import library ใหม่เข้ามา

import SimulatorStatusMagic

class SnapshotTest: XCTestCase {
var app: XCUIApplication!
override func setUp() {
cleanStatusBar()
app = XCUIApplication()
setupSnapshot(app)
app.launch()
super.setUp()
}

override func tearDown() {
SDStatusBarManager.sharedInstance().disableOverrides()
app = nil
super.tearDown()
}
}

ตั้งค่า status bar (ลบชื่อ carrier, ตั้งเวลา, ซ่อนบลูทูธ, ซ่อนข้อมูลแบต)

extension SnapshotTest {private func cleanStatusBar() {
SDStatusBarManager.sharedInstance().carrierName = ""
SDStatusBarManager.sharedInstance().timeString = "10:09"
SDStatusBarManager.sharedInstance().bluetoothState = .hidden
SDStatusBarManager.sharedInstance().batteryDetailEnabled = false
SDStatusBarManager.sharedInstance().enableOverrides()
}
}

Firebase hosting (snapshot files)

ติดตั้งและ login

$ npm install -g firebase-tools
$ firebase login

เข้าไปที่ fastlane directory เปลี่ยนชื่อ file screenshots.html เป็น index.html และตั้งค่า firebase โดยเลือก Hosting และโปรเจคที่ต้องการ หรือจะสร้างใหม่ก็ได้ครับ

$ cd fastlane
$ mv screenshots/screenshots.html screenshots/index.html
$ firebase init

และให้ config ตามนี้ครับ

What do you want to use as your public directory? screenshotsConfigure as a single-page app (rewrite all urls to /index.html)? YesFile screenshots/index.html already exists. Overwrite? No

ทดลอง ด้วยคำสั่ง $ firebase serve และ deploy ด้วย $ firebase deploy ครับ

แก้ไข fastlane/Fastfile เพื่อให้ zip ผลลัพธ์ ชื่อ results.zip และ auto deploy หลังจาก generate snapshot เรียบร้อย

lane :screenshots do
capture_screenshots(workspace: "PROJECT-NAME.xcworkspace", scheme: "SNAPSHOT-SCHEME-NAME")
sh "mv screenshots/screenshots.html screenshots/index.html"
sh "zip -r results.zip screenshots/* -x *.html* -x *.DS_Store*"
sh "mv results.zip screenshots/results.zip"
sh "firebase deploy"
end

เสร็จแล้วครับ ลอง fastlane screenshots อีกครั้ง ถ้าผ่านก็ฉลองได้เลย 🍻 ผลลัพธ์ของผมอยู่ที่นี่

ขายของหน่อยครับ

ต้องการเช่ารถ โปรดใช้บริการ Rent Connected นะครับ iOS Android website

--

--

Patompong Manprasatkul
Bug in the shell

An iOS Engineer📱investing in DEFI, FTX and ETF, interested in self driving car. @PattoMotto