HƯỚNG DẪN CÁCH AUTOMATE OTP CODE TRÊN MOBILE

Hey yo! Nay cuối năm rảnh rỗi nên mình lại viết thêm 1 bài viết dành cho các bạn nào đang làm automation test với mobile.

Câu chuyện của team mình

Câu chuyện bắt đầu bằng việc team mình có nhu cầu phải làm việc với OTP code khá nhiều, bạn nào đang thực hiện việc testing cho những ứng dụng trên nền tảng mobile chắc đều biết rằng xu hướng hiện nay là bảo mật càng nhiều càng tốt, và kéo theo việc ứng dụng nào cũng tích hợp OTP code để thực hiện bảo mật 2 lớp :(((. Đây chính là nỗi khổ của những bạn nào phải làm automate với những ứng dụng có OTP code. Đối với bạn nào dùng human testing (hay hiểu là manual testing) thì việc test những ứng dụng có OTP code không phải là vấn đề gì to lớn ngoài trừ việc đợi nhận được OTP code từ SMS

Nhưng mà với những bạn làm automate thì đó là 1 bài toán cũng tương đối lằng nhằng rườm rà để giải quyết. Chẳng hạn khi làm automate thì các bạn phải làm sao để nhận biết được SMS đã được send tới điện thoại, rồi làm sao để extract được cái OTP code từ 1 cái SMS, rồi chưa kể vài thằng ứng dụng nó chơi trò send 2-3 SMS cùng 1 lúc nhưng SMS chứa OTP code nó chỉ nằm ở trong số 3 SMS mà điện thoại đã nhận. Rồi chưa kể quá trình extract OTP code thì với những bạn cần phải test ứng dụng trên những Android/iOS version cũ thì nó chưa hỗ trợ việc tự lấy OTP từ bàn phím, hiểu nôm na là bạn phải viết thêm 1 nùi automation steps để tương tác với GUI nhằm getText cái SMS string (đậu phộng automation GUI E2E đã khó, giờ còn phải tạo mớ steps cho bước này nữa thì chắc flaky test nó tăng ào ào quá)


Nhu cầu của team mình về bài toán OTP code

Trong câu chuyện trên thì nhu cầu của team mình nó cũng đơn giản như mớ thông tin dưới đây:

  • Cần tìm 1 giải pháp để lấy OTP 1 cách lẹ nhất cũng như chính xác nhất mà không thông qua việc tương tác với GUI của mobile application
  • Có thể dễ dàng tìm và filter SMS theo số điện thoại hoạc sender address gởi tới
  • Chạy càng lẹ càng tốt (chắc chạy lẹ cỡ tốc độ bàn thờ của các bạn racing boy =]])
  • Chạy phải ổn định cũng như dễ dàng debug sau này
  • Cho phép chia sẻ điện thoại nhận SMS với những bộ test khác, hiểu nôm na là những điện thoại run test sẽ không cắm sim mà sẽ share với nhau sim từ 1 device duy nhất, device này hiểu nôm na là device chỉ dành cho mục đích nhận SMS cho những devices run test
  • Tính bảo mật tốt do việc thực hiện lấy thông tin OTP code mà lộ cơ chế lấy hoặc lữu trữ nhưng thông tin này ra ngoài thì có nguy cơ bị hacker lợi dụng để làm trò bậy bạ gì đó =)))

Sau khi ngồi thao luận và list ra được mớ yêu cầu trên thì cái mặt của mình nó y chang như vậy

Mình ngồi nghĩ trong đầu “yêu cầu gì mà yêu cầu lắm yêu cầu lốn”, nhưng mà cũng phải tìm giải pháp thôi vì những cái yêu cầu đó nó hợp lý quá rồi :((((((

Sự ra đời của sms-listener-service plugin

Sau nhiều đêm trằn trọc thao thức, suy nghĩ những cách để giải quyết mớ yêu cầu trên. Tốn hết mấy chục ly cafe cũng như tiền net, mình đã quyết định sẽ implement 1 cái plugin (hiểu nôm na là 1 native application chạy trên device) nhằm mục đích có thể call Restful API từ plugin đó để lấy được SMS từ device. Hiểu nôm na thì cơ chế nó sẽ giống như 2 cái hình dưới đây:

High level sms-listener-service overview
SMS Listener Service Application Overview

Như mình mô tả ở 2 hình trên thì mình sẽ thực hiện việc viết 1 ứng dụng (plugin version hiện tại thì mình chỉ mới viết ứng dụng đó trên nền tản Android), trong đó nó sẽ chạy 1 http server (backend) bên trong chính điện thoại dưới dạng 1 background service foreground service. Server trên sẽ expose 3 APIs dạng Restful để cho users có thể thực hiện việc truy xuất SMS đang có trên devices. Chắc lúc này sẽ có vài bạn thắc mắc vì sao mình đi với lựa chọn này =)) Thì dưới đây là những lý do mình đi với lựa chọn này:

  • Mô hình dựng 1 backend bên trong device thì mình cũng dựa vào cơ chế của Appium driver, và trong tương lai với Appium 2.0 thì cơ chế này bên Appium sẽ gọi là custom driver (đọc thêm bài này nếu các bạn quan tâm về Appium 2.0)
  • Việc chạy 1 backend bên trong mobile chỉ có nhiệm vụ nhận request từ Restful APIs rồi trả về data SMS cho users sẽ giúp cho việc lấy thông tin code OTP trở nên lẹ và dễ dàng hơn, mình sẽ không cần thực hiện những bước liên quan tới GUI chỉ để lấy được SMS text
  • Mình chọn netty server vì hiện tại có tìm hiểu những thư viện serverless nhúng vô mobile thì không có nhiều sự lựa chọn lắm ngoài 2 thằng (có thể mình tìm bị sót nên nếu ai biết thư viện nào nữa thì comment share mình nhé) nanohttpdnetty.io. Lý do mình chọn netty.io cũng là do hiện tại Appium driver agent trên Android cũng đang sử dụng thư viện này, tội gì không sử dụng 1 thư viện đã được Appium kiểm tra và sử dụng chính thức =))
  • Lý do mình chọn implement plugin application này dưới dạng Android service (background service foreground service) là do việc khi sử dụng cho việc automate mobile application thì mình application được automate sẽ chạy ở main process của mobile deivce, dẫn tới việc nếu không chạy plugin application dưới dạng background service foreground service (ban đầu mình làm background service tuy nhiên hiện tại ở những Android version mới thì nó sẽ bị tình trạng điện thoại rơi vào Doze/Standby mode thì service mình sẽ bị kill vì thế mình đã chuyển sang foreground service) thì http server sẽ bị tắt, và kéo theo việc mình không thể nào truy cập được SMS của device được nữa
  • Tới đây mà bạn nào từng làm làm việc nhiều với ứng dụng Android sẽ thắc mắc là tại sao mình lại không implement plugin của mình dưới dạng “Incomming SMS Broadcast Receiver”. Khi đó mình sẽ không cần duy trì 1 http server bên trong mobile device. Thiệt ra thì cách này cũng được, chỉ khác là lúc này mình lại phải implement 1 cái http server ở ngoài mobile deivce chỉ để nó nhận và lưu trữ SMS được forward từ mobile device ra. Hiểu nôm na là cái application plugin mỗi lần có new SMS được send tới điện thoại thì nó sẽ tự động call 1 API từ cái server mình dựng từ bên ngoài để lưu trữ thông tin SMS đó ra ngoài (cơ chế nó sẽ tương tự ứng dụng này mysms). Về cơ chế thì nó tương tự nhau, nhưng cách này thì sẽ mất thời gian implement thêm và dựng tiếp 1 cái http server ở ngoài, bên cạnh đó cũng phải làm thêm cơ chế lưu trữ SMS được forwad từ điện thoại qua server đó. Dẫn tới việc phải nghĩ tới vấn đề bảo mật cho những thông tin OTP đó. Trong khi với việc chạy 1 http server bên trong điện thoại thì khi nào users có nhu cầu muốn lấy thông tin SMS thì plugin application chỉ cần đọc thông tin SMS của device và trả về luôn, việc lưu trữ những thông tin SMS sẽ không cần thiết nữa. Bên cạnh đó mình cũng muốn loại bỏ luôn yếu tố việc sync SMS từ điện thoại qua external backend bị lỗi dẫn tới có thể điện thoại nhận được SMS nhưng quá trình sync SMS từ device qua hệ thống backend external bị lỗi.

Sử dụng sms-listener-service plugin

Nãy giờ mình nói dông dài rồi, giờ thì mình vô tới phần hướng dẫn sử dụng cái application này. Nói chung việc sử dụng nó cũng dễ dàng chứ không phức tạp gì mấy.

1. Tải plugin android application từ link này

2. Kết nối điện thoại Android với máy tính của bạn và nhớ bật đầy đủ các mode hỗ trợ việc sử dụng adb command nha các anh em. Bạn nào chưa biết làm những bước gì thì có thể coi bài viết này

3. Kiểm tra thử android device máy tính đã kết nối được chưa thông qua lệnh sau

adb devices
Kiểm tra máy tính đã kết nối được với device chưa

4. Tiến hành cài đặt plugin application vô điện thoại thông qua lệnh sau

adb install -g sms-listener.apk

Ở đây các bạn nhớ phải có argument “-g” nhé. Do application sẽ đòi hỏi những permission cho việc đọc và truy cập SMS messages. Nếu các bạn không install với argument “-g” thì ứng dụng sẽ không thể chạy được

adb install options
Cài đặt plugin application vô device

5. Start application từ adb command, các bạn cũng có thể start từ việc open nó trên GUI app, nhưng ở đây mình chọn cách hướng dẫn việc start application từ adb command để các bạn có thể dễ dàng kết hợp với automation scripts của mình 1 cách dễ dàng

adb shell am start -n "com.toilatester.smslistener/com.toilatester.sms.listener.MainActivity" --ei serverPort 8185

Ở đây argument “–ei” cho phép bạn cấu hình server port sẽ chạy bên trong device. Mục đích của việc này là cho phép bạn có thể sử dụng nhiều devices cùng 1 lúc trên 1 máy tính. Do để có thể truy cập vô được APIs của plugin http server trên device thì các bạn phải truy cập qua 2 cách.

  • Dùng lệnh [adb forward tcp:8181 tcp:8185] để forward request từ máy đang kết nối device vô bên trong http server của device. Như lệnh ở trên có nghĩa là forward request được gọi tới địa chỉ localhost của máy tính ở port 8181 vô server đang được start bên trong device ở port 8185
  • Truy cập APIs thông qua IP của device đang chạy. Ví dụ: khi bạn đang connect device với wifi thì lúc này device sẽ có 1 địa chỉ IP, khi đó bạn chỉ cầng gọi trực tiếp APIs thông qua URL sau: http://địa_chỉ_ip_của_device:port_đang_start_http_server/{api_enpoints}
Start plugin application

Vậy là quá trình hướng dẫn cũng đã xong, giờ thì các bạn tận hưởng thành quả thôi

Demo plugin application khi sử dụng

Những câu hỏi liên quan tới quá trình plugin chạy

Liệu plugin có lén lấy trộm thông tin SMS của người dùng không

Trả lời: Không.

Giải thích: Đó là lý do mình không chọn cách implement với “Incomming SMS Broadcast Receiver”, vì khi đó mình cần xây dựng 1 hệ thống bên ngoài để lưu trữ những thông tin SMS để sau đó users có thể truy xuất thông tin SMS từ device thông qua Restful APIs. Do plugin của mình là 1 http server nó chỉ có nhiệm vụ nhận request từ users, truy cập thông tin SMS và trả về dưới dạng JSON data cho nên nó không hề lưu thông tin đó ở bất kỳ chỗ nào khác được. Các bạn có thể dễ dàng kiểm chứng thông qua source code =)) vì mình cũng open source cái plugin này

Việc chạy background service foreground service thì có bị tình trạng plugin application bị kill khi đang chạy

Trả lời: Có và Không

Giải thích: Hiện tại mình start background service foreground service với mode “START_STICKY”. Theo như document mình đọc thì với mode này thì khi điện thoại đang bi low memory nó sẽ có thể tự đông kill bớt 1 vài ứng dụng có mức độ ưu tiên không cao. Khi đó với mode “START_STICKY” thì ứng dụng sẽ tự động chạy lại service sms-listener 1 cách tự động. Hiện tại thì application của mình quá trình chạy sẽ không tốn quá nhiều memory của device, lý do là chỉ nhận request và trả về data liền chứ không hề lưu hay phát sinh thêm dữ liệu, từ đó khả năng dẫn tới việc gây tiêu tốn quá nhiều memory của device làm cho device tự động kill serivce là không có. Tuy nhiên có chỗ này lưu ý, nếu bạn tắt hẳn ứng dụng thì đó lại là việc khác, nó tương tự việc bạn sử dụng ứng dụng nghe nhạc, nó chạy ngầm ở hệ thống, khi nhấn vào nút recent app trên các điện thoại android và vút để tắt nó đi thì đồng nghĩa bạn đã force tắt service, khi đó thì mình cũng chịu thua do bạn đã cố ý tắt ứng dụng rồi. Nhưng dù sao thì mình cũng sẽ test thêm vấn đề này, tuy nhiên nếu phục vụ mục đích automate thì mình nghĩ nó không phải là vấn đề lớn, mỗi khi test start bạn cứ execute command start plugin service để đảm bảo plugin chạy là coi như giảm thiểu khả năng plugin không chạy rồi. Mình đã có test thử trên device android Samsung galaxy S7 Edge của mình thì service sẽ luôn chạy kể cả khi bạn nhấn button recent và close application. Lý do ở trên mình có đề cập việc nhấn recent và close ứng dụng có thể bị tắt service là do thông tin từ bài viết này (bạn nào xài thử mà có bị thì có thể báo lại mình nha)

Khi nào sẽ có plugin này trên iOS

Trả lời: Chưa biết nữa tùy theo mức độ rãnh rang của mình =))

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s