ADB + FFmpeg के साथ एक Android डिवाइस फार्म: 10–50 डिवाइसों पर स्वचालित UI टेस्टिंग

@ridark_eth
अंग्रेज़ी1 दिन पहले · 07 जुल॰ 2026
209K
104
10
22
175

TL;DR

यह गाइड ADB और FFmpeg का उपयोग करके दर्जनों Android डिवाइसों पर APK इंस्टॉलेशन, UI टेस्टिंग और वीडियो रिपोर्टिंग को स्वचालित करने के लिए एक Python पाइपलाइन प्रदान करती है, जिसमें विस्तृत लागत-लाभ विश्लेषण भी शामिल है।

जब आपके ऐप को दर्जनों वास्तविक फोनों, विभिन्न विक्रेताओं, Android संस्करणों और स्क्रीन रिज़ॉल्यूशन के चिड़ियाघर पर काम करना होता है, तो मैन्युअल परीक्षण जल्दी ही एक दुःस्वप्न में बदल जाता है। नीचे एक Python पाइपलाइन दी गई है जो अपने आप सभी कनेक्टेड डिवाइसों को खोजती है, उन सभी पर समानांतर रूप से APK इंस्टॉल करती है, इंस्ट्रूमेंटेड टेस्ट चलाती है, प्रत्येक रन का वीडियो रिकॉर्ड करती है, और FFmpeg के साथ उन रिकॉर्डिंग्स को एक एकल वीडियो रिपोर्ट में जोड़ती है।

पूरा स्टैक > Python + ADB + FFmpeg < मानक QA टूलिंग है। कोई जादू नहीं, बस ग्राइंड को ऑटोमेट करना।

Ridark - inline image

पाइपलाइन आर्किटेक्चर

text
1adb devices ──► सीरियल की सूची
2
3
4APK इंस्टॉल (सभी डिवाइसों पर समानांतर, ThreadPoolExecutor)
5
6
7प्रत्येक डिवाइस के लिए:
8 screenrecord (बैकग्राउंड में) → am instrument (टेस्ट चलाएँ) → रोकें + वीडियो खींचें
9
10
11FFmpeg: प्रत्येक क्लिप पर सीरियल ओवरले करें + जोड़ें ──► test_report.mp4

आपको क्या चाहिए होगा

  • ADB (Android Debug Bridge) Android Platform Tools से -> डिवाइस नियंत्रण।
  • Python 3.10+ -> ऑर्केस्ट्रेशन (मैं list[str], tuple[...] का उपयोग करता हूँ बिना from __future__ के)।
  • FFmpeg -> वीडियो प्रोसेसिंग और असेंबली।
  • USB डिबगिंग सक्षम वाले डिवाइस, USB (या adb tcpip के माध्यम से वाई-फाई पर) से कनेक्टेड।

एक सिद्धांत जो सभी कोड में चलता है: मैं सबप्रोसेस को तर्क एक सूची के रूप में और shell=True के बिना पास करता हूँ। यह सुरक्षित है (फ़ाइल नामों के माध्यम से कोई इंजेक्शन नहीं) और यह पथों में रिक्त स्थान या विशेष वर्णों पर टूटता नहीं है।

1. डिवाइस डिस्कवरी

Ridark - inline image

adb devices अनधिकृत/ऑफ़लाइन स्थिति में डिवाइसों को भी सूचीबद्ध करता है। हम केवल उन्हीं को रखते हैं जो वास्तव में डिवाइस स्थिति में हैं।

python
1import subprocess
2
3def get_devices() -> list[str]:
4 """'डिवाइस' स्थिति में सभी डिवाइसों के सीरियल लौटाएँ।"""
5 out = subprocess.run(
6 ["adb", "devices"],
7 capture_output=True, text=True, check=True,
8 ).stdout
9
10 serials: list[str] = []
11 for line in out.splitlines()[1:]: # पहली पंक्ति "List of devices" हेडर है
12 line = line.strip()
13 if line.endswith("\tdevice"): # अनधिकृत/ऑफ़लाइन को हटाएँ
14 serials.append(line.split("\t")[0])
15 return serials

2. समानांतर APK इंस्टॉल

एक-एक करके 50 डिवाइसों पर इंस्टॉल करना धीमा है। हम काम को थ्रेड पूल में फैलाते हैं: प्रत्येक adb install एक अलग प्रक्रिया है, इसलिए थ्रेड यहाँ बहुत अच्छा काम करते हैं (हम I/O पर प्रतीक्षा कर रहे हैं, CPU नहीं जला रहे)।

python
1from concurrent.futures import ThreadPoolExecutor, as_completed
2
3def install_apk(serial: str, apk_path: str) -> tuple[str, bool, str]:
4 r = subprocess.run(
5 ["adb", "-s", serial, "install", "-r", "-g", apk_path],
6 capture_output=True, text=True,
7 )
8 ok = r.returncode == 0 and "Success" in r.stdout
9 return serial, ok, (r.stdout + r.stderr).strip()
10
11def install_on_all(apk_path: str, serials: list[str]) -> None:
12 with ThreadPoolExecutor(max_workers=len(serials) or 1) as pool:
13 futures = [pool.submit(install_apk, s, apk_path) for s in serials]
14 for f in as_completed(futures):
15 serial, ok, log = f.result()
16 print(f"[{'OK' if ok else 'FAIL'}] {serial}")
17 if not ok:
18 print(f" {log}")

फ़्लैग: -r -> डेटा रखते हुए पुनः इंस्टॉल करें, -g -> सभी रनटाइम अनुमतियाँ तुरंत प्रदान करें (सुविधाजनक ताकि टेस्ट अनुमति डायलॉग में न फँसें)।

3. इंस्ट्रूमेंटेड टेस्ट चलाना

am instrument डिवाइस पर Espresso/JUnit टेस्ट चलाता है। यह सफलता पर OK और विफलता पर FAILURES!!! stdout पर प्रिंट करता है -> इसी से हम परिणाम निर्धारित करते हैं।

python
1def run_instrumented_tests(
2 serial: str,
3 package: str,
4 runner: str = "androidx.test.runner.AndroidJUnitRunner",
5) -> tuple[str, bool]:
6 r = subprocess.run(
7 ["adb", "-s", serial, "shell", "am", "instrument", "-w",
8 f"{package}/{runner}"],
9 capture_output=True, text=True,
10 )
11 ok = "FAILURES!!!" not in r.stdout and r.returncode == 0
12 return serial, ok

package टेस्ट पैकेज ID है, आमतौर पर com.example.app.test।

4. टेस्ट के दौरान स्क्रीन रिकॉर्ड करना

screenrecord डिवाइस पर ही वीडियो रिकॉर्ड करता है। ध्यान रखने योग्य सीमाएँ: प्रति फ़ाइल लगभग 3 मिनट की कैप और कोई ऑडियो नहीं। हम बैकग्राउंड में रिकॉर्डिंग शुरू करते हैं, टेस्ट चलाते हैं, फिर इसे साफ-साफ रोकते हैं और फ़ाइल को होस्ट पर खींचते हैं।

रिकॉर्डिंग रोकने का सबसे विश्वसनीय तरीका स्थानीय adb को सिग्नल देना नहीं है, बल्कि डिवाइस पर ही pkill का उपयोग करना है -> इस तरह screenrecord MP4 कंटेनर को सही ढंग से फ़ाइनल करता है।

python
1import time
2
3def start_recording(serial: str, remote: str = "/sdcard/run.mp4") -> subprocess.Popen:
4 return subprocess.Popen(
5 ["adb", "-s", serial, "shell", "screenrecord", remote]
6 )
7
8def stop_recording(
9 serial: str,
10 proc: subprocess.Popen,
11 remote: str = "/sdcard/run.mp4",
12 local: str = "run.mp4",
13) -> None:
14 # डिवाइस पर SIGINT screenrecord को फ़ाइल को ठीक से बंद करने देता है
15 subprocess.run(["adb", "-s", serial, "shell", "pkill", "-SIGINT", "screenrecord"])
16 proc.wait(timeout=10)
17 time.sleep(1) # डिवाइस को कंटेनर फ़ाइनल करने का समय दें
18 subprocess.run(["adb", "-s", serial, "pull", remote, local], check=True)

5. FFmpeg के साथ वीडियो रिपोर्ट असेंबल करना

अलग-अलग डिवाइसों के अलग-अलग स्क्रीन रिज़ॉल्यूशन होते हैं, इसलिए आप उन्हें -c copy के साथ सीधे जोड़ नहीं सकते। हम प्रत्येक क्लिप को एक सामान्य प्रारूप (1080×1920) में सामान्यीकृत करते हैं और रास्ते में drawtext के माध्यम से सीरियल को ओवरले करते हैं। उसके बाद सभी क्लिप समान हो जाती हैं और अंतिम असेंबली बिना पुनः-एन्कोडिंग के एक तेज़ concat है।

python
1def label_clip(src: str, dst: str, label: str) -> None:
2 """1080x1920 पर स्केल करें और एक लेबल (डिवाइस सीरियल) ओवरले करें।"""
3 vf = (
4 "scale=1080:1920:force_original_aspect_ratio=decrease,"
5 "pad=1080:1920:(ow-iw)/2:(oh-ih)/2,"
6 f"drawtext=text='{label}':x=20:y=20:fontsize=42:"
7 "fontcolor=white:box=1:[email protected]"
8 )
9 subprocess.run(
10 ["ffmpeg", "-y", "-i", src, "-vf", vf,
11 "-an", "-c:v", "libx264", "-preset", "veryfast", "-crf", "23", dst],
12 check=True,
13 )
14
15def concat_report(clips: list[str], out: str = "test_report.mp4") -> None:
16 with open("concat_list.txt", "w") as f:
17 for c in clips:
18 f.write(f"file '{c}'\n")
19 subprocess.run(
20 ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
21 "-i", "concat_list.txt", "-c", "copy", out],
22 check=True,
23 )

6. सब कुछ एक साथ रखना

python
1def main() -> None:
2 apk = "app-debug.apk"
3 package = "com.example.app.test"
4 runner = "androidx.test.runner.AndroidJUnitRunner"
5
6 serials = get_devices()
7 if not serials:
8 print("कोई डिवाइस नहीं मिला। USB और 'adb devices' का आउटपुट जाँचें।")
9 return
10
11 print(f"डिवाइस मिले: {len(serials)}")
12 install_on_all(apk, serials)
13
14 labeled: list[str] = []
15 for serial in serials:
16 proc = start_recording(serial)
17 _, ok = run_instrumented_tests(serial, package, runner)
18 stop_recording(serial, proc, local=f"{serial}.mp4")
19 print(f"[{'PASS' if ok else 'FAIL'}] {serial} पर टेस्ट")
20
21 out = f"{serial}_labeled.mp4"
22 label_clip(f"{serial}.mp4", out, serial)
23 labeled.append(out)
24
25 concat_report(labeled, "test_report.mp4")
26 print("हो गया: test_report.mp4")
27
28if __name__ == "__main__":
29 main()

यहाँ "रिकॉर्ड + टेस्ट" लूप डिवाइसों पर क्रमिक रूप से चलता है -> यह इस तरह से अधिक पठनीय है। एक वास्तविक फार्म के लिए आप इस ब्लॉक को ThreadPoolExecutor में लपेटना चाहेंगे, ताकि सभी डिवाइसों का एक साथ परीक्षण हो; तर्क सेक्शन 2 में इंस्टॉल के समान है।

पहिया का पुनः आविष्कार न करें: तैयार उपकरण

Ridark - inline image
  • scrcpy -> अपने पीसी से डिवाइस का रीयल-टाइम मिररिंग और नियंत्रण। विफल टेस्ट को डीबग करते समय अपरिहार्य।
  • Appium / Espresso / UI Automator -> पूर्ण विकसित UI परीक्षण फ्रेमवर्क; ऊपर दिया गया am instrument उनका इंजन है।
  • Gradle Managed Devices -> बिल्ड से सीधे एमुलेटर पर टेस्ट चलाएँ, बिना मैन्युअल ADB हैंडलिंग के।
  • Firebase Test Lab / AWS Device Farm -> वास्तविक डिवाइसों का क्लाउड फ्लीट यदि आप अपना हार्डवेयर नहीं रखना चाहते।
  • GNU parallel -> यदि आप Python के बजाय bash से ऑर्केस्ट्रेट करना पसंद करते हैं।

अर्थशास्त्र: इसकी लागत क्या है और यह क्या बचाता है

टेस्ट ऑटोमेशन "हवा से पैसा कमाने" के बारे में नहीं है -> यह दो सबसे महंगी मदों को कम करने के बारे में है: व्यक्ति-घंटे और क्लाउड मिनट। नीचे तीन विशिष्ट पैमानों पर एक अनुमान है। संख्याएँ उदाहरणात्मक हैं और क्षेत्र, डिवाइस विक्रेता और प्रदाता मूल्य निर्धारण पर निर्भर करती हैं, खरीदने से पहले वर्तमान दरों की जाँच करें।

आपका अपना फार्म -> एकमुश्त निवेश

आइटम

10 डिवाइसों के लिए

30 डिवाइसों के लिए

50 डिवाइसों के लिए

प्रयुक्त Android फ़ोन (~$60 प्रति)

~$600

~$1,800

~$3,000

पावर्ड USB हब

~$100

~$250

~$400

मिनी-पीसी / होस्ट

~$400

~$400

~$500

केबल, रैक, अन्य सामान

~$80

~$150

~$250

एकमुश्त कुल

~$1,200

~$2,600

~$4,150

प्रति माह बिजली

पैसे

~$10–20

~$20–40

यह पूंजीगत व्यय है: एक बार भुगतान करें, और फिर फार्म वर्षों तक लगभग बिना किसी लागत के चलता है।

क्लाउड -> आप प्रति मिनट भुगतान करते हैं

Firebase Test Lab, AWS Device Farm, BrowserStack, और इसी तरह की सेवाएँ प्रति डिवाइस-मिनट शुल्क लेती हैं -> लगभग $0.05–0.20 प्रति डिवाइस-मिनट। 30 डिवाइसों पर 5 मिनट प्रत्येक का एक एकल रिग्रेशन रन 150 डिवाइस-मिनट है, यानी लगभग $7.5–30 प्रति रन।

अब CI तीव्रता से गुणा करें:

रन आवृत्ति

प्रति माह रन

लागत (~$15/रन पर)

2× प्रति दिन

~44

~$660/माह

10× प्रति दिन

~220

~$3,300/माह

हर पुश पर (सक्रिय टीम)

500+

$7,500+/माह

ब्रेक-ईवन: एक 30-डिवाइस फार्म (~$2,600) मामूली 2 रन प्रति दिन पर लगभग 4 महीनों में क्लाउड के मुकाबले अपनी लागत वसूल कर लेता है; सक्रिय CI के साथ, एक महीने से भी कम समय में। उसके बाद क्लाउड बिल हर महीने टपकता रहता है, और फार्म नहीं।

मैन्युअल श्रम -> क्या खाली होता है

30 डिवाइसों पर एक एकल रिग्रेशन परिदृश्य का मैन्युअल रन लगभग एक QA इंजीनियर के कार्यदिवस के बराबर है। सप्ताह में दो बार रिग्रेशन चलाने से प्रति माह लगभग 8 व्यक्ति-दिन जुड़ते हैं। लगभग $1,600–2,700/माह की QA लागत पर, यह वेतन का एक महत्वपूर्ण हिस्सा है जिसे पाइपलाइन "कनेक्ट-इंस्टॉल-पोक-रिकॉर्ड" ग्राइंड ×30 के बजाय सार्थक काम के लिए मुक्त करती है।

यह पैसे में कैसे बदलता है

यहाँ "स्क्रिप्ट से" कोई सीधी आय नहीं है -> यहाँ तीन अप्रत्यक्ष लेकिन बहुत वास्तविक तंत्र हैं:

  • तेज़ रिलीज़। एक दिन के बजाय मिनटों में रिग्रेशन → आप अधिक बार फीचर शिप करते हैं → आप बाजार पर तेज़ी से प्रतिक्रिया करते हैं। एक सब्सक्रिप्शन उत्पाद के लिए, यह सीधे प्रतिधारण और राजस्व से जुड़ता है।
  • प्रोडक्शन में कम बग। रिलीज़ से पहले किसी विशिष्ट Samsung पर क्रैश को पकड़ने में पैसे खर्च होते हैं; उपयोगकर्ताओं तक पहुँचने वाला वही क्रैश स्टोर रेटिंग, चर्न और रिफंड को गिरा देता है। जल्दी पकड़ा गया हर बग नकारात्मक समीक्षाओं का एक समूह है जो कभी नहीं लिखी जाती।
  • यह एक सेवा के रूप में बिकता है। डिवाइस फार्म और CI परीक्षण स्थापित करना फ्रीलांसिंग और आउटसोर्सिंग में एक मांग वाली भूमिका है। ऊपर दी गई पाइपलाइन ऐसी पेशकश के लिए एक तैयार कोर है।

"एंगेजमेंट-फार्मिंग स्कीम" से मुख्य अंतर: वहाँ, पैसा एल्गोरिदम को धोखा देने से आता है और प्रतिबंध में समाप्त होता है। यहाँ, यह बचाए गए घंटों और रोके गए नुकसान से आता है। पहला टूट जाता है; दूसरा एक स्थायी व्यावसायिक मामला है जिसे आप किसी ग्राहक को दिखाने में शर्मिंदा नहीं होंगे।

सारांश

परिणाम एक प्रतिलिपि प्रस्तुत करने योग्य पाइपलाइन है: एक कमांड, और ऐप पूरे डिवाइस फ्लीट पर चलाया जाता है, अंत में एक वीडियो रिपोर्ट के साथ जो प्रत्येक विशिष्ट डिवाइस पर व्यवहार दिखाती है।

बुकमार्क करें और सब्सक्राइब करें ताकि आप पैसे बचाने वाले उपयोगी लेखों को न चूकें 📝

एक क्लिक में सहेजें

YouMind में वायरल लेखों की AI गहन पढ़ाई

Save the source, ask focused questions, summarize the argument, and turn a viral article into reusable notes in one AI workspace.

Explore YouMind
क्रिएटर्स के लिए

अपने Markdown को एक साफ़-सुथरे 𝕏 आर्टिकल में बदलें

जब आप अपना लंबा कंटेंट पब्लिश करते हैं, तो इमेज, टेबल और कोड ब्लॉक को 𝕏 के लिए फ़ॉर्मेट करना मुश्किल होता है। YouMind पूरे Markdown ड्राफ़्ट को एक साफ़-सुथरे, पोस्ट के लिए तैयार 𝕏 आर्टिकल में बदल देता है।

Markdown से 𝕏 आज़माएँ

समझने के लिए और पैटर्न

हाल के वायरल लेख

और वायरल लेख देखें