تست موبایل یکی از چالشبرانگیزترین بخشهای QA است. برخلاف تست وب که فقط یک مرورگر دارید، در موبایل باید با تنوع دستگاهها، نسخههای مختلف Android، رفتار متفاوت iOS، ژستهای لمسی، چرخش صفحه، و اعلانهای سیستمی کنار بیایید.
نرم افزار TestComplete این پیچیدگی را با یک رویکرد یکپارچه مدیریت میکند — همان ابزاری که تست وب و دسکتاپ شما را اجرا میکند، تست اپلیکیشن موبایل را هم میچرخاند. یک پروژه، یک گزارش، یک pipeline.
در این مقاله با سناریوی واقعی اپلیکیشن موبایل آرینشاپ — همان فروشگاه آنلاینی که در آموزش تست UI و تست API با آن آشنا شدیم — گامبهگام یاد میگیرید که چطور یک framework تست موبایل کامل بسازید.
چرا تست موبایل با TestComplete؟
قبل از شروع، یک مقایسه سریع با گزینههای دیگر:
Appium رایگان و محبوب است اما نیاز به تنظیمات پیچیده دارد — Node.js، Appium Server، driver های مختلف برای Android و iOS، و یک test framework جداگانه. برای تیمهایی که از TestComplete برای وب استفاده میکنند، این یعنی یادگیری یک stack کاملاً جدید.
TestComplete Mobile در عوض:
- در همان IDE که وب و دسکتاپ را تست میکنید کار میکند
- تستهای Android و iOS با همان syntax Python/JavaScript نوشته میشوند
- نتایج هر سه پلتفرم در یک گزارش یکپارچه نمایش داده میشوند
- اگر قبلاً با TestComplete کار کردهاید، منحنی یادگیری موبایل بسیار کوتاه است
معرفی سناریو: اپلیکیشن موبایل آرینشاپ
اپلیکیشن موبایل آرینشاپ یک اپلیکیشن native برای Android و iOS است که کاربران میتوانند از طریق آن:
- محصولات را جستجو و فیلتر کنند
- تصاویر محصول را با swipe مرور کنند
- کالا را به سبد خرید اضافه کنند
- از طریق درگاه پرداخت موبایلی خرید کنند
- وضعیت سفارش را دنبال کنند
مشکل تیم: قبل از هر release، تستر باید همه این مسیرها را روی حداقل ۳ دستگاه Android با اندازه صفحه متفاوت و یک iPhone تست کند. این کار ۲ روز وقت میبرد. با اتوماسیون، هدف رساندن آن به ۳ ساعت است.
گام اول: راهاندازی محیط تست Android
نیازمندیهای سیستم
قبل از شروع، مطمئن شوید که روی کامپیوتر تست شما موجود است:
- TestComplete 15 با ماژول Mobile فعال (راهنمای خرید لایسنس)
- Android SDK — میتوانید فقط Platform Tools را نصب کنید، نیازی به Android Studio کامل نیست
- ADB (Android Debug Bridge) — در مسیر PATH سیستم
- Java JDK 11 یا بالاتر
برای تست روی دستگاه واقعی، یک گوشی Android با Developer Options فعال و USB Debugging روشن.
فعالسازی Developer Options روی Android
- به Settings → About Phone بروید
- روی Build Number هفت بار ضربه بزنید
- به Settings → Developer Options برگردید
- USB Debugging را روشن کنید
- گوشی را با کابل USB به کامپیوتر وصل کنید
- در پنجرهای که روی گوشی ظاهر میشود، Allow USB Debugging را تأیید کنید
تأیید اتصال با ADB:
adb devices
# خروجی باید چیزی شبیه این باشد:
# List of devices attached
# R58M3xxxxx device
اگر دستگاه به جای device نوشته unauthorized، روی گوشی گزینه Allow را بزنید.
نصب TestComplete Agent روی دستگاه Android
TestComplete برای ارتباط با اپلیکیشن موبایل به یک agent روی دستگاه نیاز دارد:
- در TestComplete از منوی Tools → Mobile گزینه Install Agent on Android Device را انتخاب کنید
- دستگاه متصل شما در لیست نمایش داده میشود
- روی Install کلیک کنید
- بعد از نصب، agent را روی دستگاه یکبار اجرا کنید
گام دوم: ساخت پروژه موبایل در TestComplete
یک پروژه جدید بسازید:
- از منوی File گزینه New Project را انتخاب کنید
- نوع پروژه را Mobile انتخاب کنید
- Platform را Android انتخاب کنید
- نام پروژه را
ArinShopMobileTestsبگذارید - روی Finish کلیک کنید
اضافه کردن اپلیکیشن به پروژه
در Project Explorer روی Mobile Devices کلیک راست کنید و Add → Android Application را انتخاب کنید. دو گزینه دارید:
- APK File — فایل نصبی اپلیکیشن را مستقیماً آپلود کنید. TestComplete آن را روی دستگاه نصب و بعد از تست پاک میکند.
- Installed Application — اگر اپلیکیشن از قبل روی دستگاه نصب است، package name را وارد کنید (مثلاً
com.arinshop.android)
برای محیط CI/CD، گزینه APK File بهتر است — فایل APK در repository نگه میدارید و همیشه نسخه مشخصی تست میشود.
💬 برای دریافت قیمت و مشاوره رایگان → همین الان در تلگرام پیام دهید — پاسخ در کمتر از چند ساعت
گام سوم: Name Mapping برای عناصر موبایل
قبل از نوشتن هر تستی، باید Name Mapping عناصر اصلی اپلیکیشن را تعریف کنید. این کار در موبایل کمی با وب متفاوت است.
TestComplete برای شناسایی عناصر Android از UI Automator استفاده میکند. هر عنصر دارای مشخصاتی مثل resource-id، content-desc، text، و className است.
استفاده از Mobile Screen برای بررسی عناصر
- دستگاه را متصل کنید و اپلیکیشن را باز کنید
- در TestComplete از منوی Tools گزینه Mobile Screen را انتخاب کنید
- یک نمای live از صفحه دستگاه باز میشود
- روی هر عنصر کلیک کنید تا مشخصات آن را ببینید
برای صفحه اصلی آرینشاپ، این mapping ها را تعریف کردیم:
| نام در Name Mapping | resource-id یا locator | توضیح |
|---|---|---|
HomePage.SearchBar |
com.arinshop.android:id/search_input |
نوار جستجو |
HomePage.ProductList |
com.arinshop.android:id/product_recycler |
لیست محصولات |
HomePage.CartIcon |
com.arinshop.android:id/cart_icon |
آیکون سبد خرید |
ProductPage.AddToCart |
com.arinshop.android:id/btn_add_to_cart |
دکمه افزودن |
ProductPage.ImageGallery |
com.arinshop.android:id/image_pager |
گالری تصویر |
CartPage.CheckoutBtn |
com.arinshop.android:id/btn_checkout |
دکمه پرداخت |
CartPage.TotalPrice |
com.arinshop.android:id/total_price_text |
مبلغ کل |
نکته مهم برای اپلیکیشنهای فارسی: اگر اپلیکیشن دارای متن فارسی در content-desc یا text است، TestComplete آن را کاملاً پشتیبانی میکند. اما توصیه میشود از resource-id برای شناسایی استفاده کنید — پایدارتر است و به تغییر متن حساس نیست.
گام چهارم: ضبط اولین تست موبایل
روی دکمه Record در تولبار کلیک کنید. TestComplete میپرسد روی کدام دستگاه ضبط انجام شود — دستگاه متصل خود را انتخاب کنید.
حالا روی گوشی واقعیتان این مراحل را انجام دهید:
- اپلیکیشن آرینشاپ را باز کنید
- در نوار جستجو «کفش» را تایپ کنید
- اولین محصول از لیست نتایج را باز کنید
- روی «افزودن به سبد» ضربه بزنید
- به صفحه سبد خرید بروید
- مبلغ کل را ببینید
- روی Stop کلیک کنید
TestComplete یک اسکریپت Python از این مراحل میسازد که میتوانید آن را ویرایش و بهینه کنید.
گام پنجم: نوشتن تست با کد — جستجو و خرید
بعد از ضبط، تست را با اضافه کردن Checkpoint ها و منطق اعتبارسنجی کامل کنید:
def Test_SearchAndAddToCart():
"""
سناریو: جستجوی محصول و افزودن به سبد خرید
دستگاه: Android
"""
# دریافت رابط دستگاه متصل
device = Mobile.Device("ArinShop_TestDevice")
device.Activate()
# اطمینان از اینکه اپلیکیشن در صفحه اصلی است
app = device.Application("com.arinshop.android")
app.Launch()
aqUtils.Delay(2000) # صبر برای لود شدن اپلیکیشن
# ---- مرحله ۱: جستجوی محصول ----
searchBar = app.FindElement("HomePage.SearchBar")
searchBar.Touch()
searchBar.SetText("کفش ورزشی")
# بستن کیبورد و ارسال جستجو
device.Keys("[Enter]")
aqUtils.Delay(1500)
# Checkpoint: نتایج جستجو باید نمایش داده شود
productList = app.FindElement("HomePage.ProductList")
aqVerification.IsTrue(
productList.IsVisible(),
"لیست نتایج جستجو باید نمایش داده شود"
)
itemCount = productList.ChildCount
aqVerification.IsTrue(
itemCount > 0,
"نتایج جستجو نباید خالی باشد — " + str(itemCount) + " آیتم"
)
Log.Message("✓ " + str(itemCount) + " نتیجه برای 'کفش ورزشی' یافت شد")
# ---- مرحله ۲: باز کردن محصول اول ----
firstProduct = productList.Child(0)
firstProductName = firstProduct.FindChild("product_name_text").Text
firstProduct.Touch()
aqUtils.Delay(1000)
# Checkpoint: صفحه محصول باز شده
addToCartBtn = app.FindElement("ProductPage.AddToCart")
aqVerification.IsTrue(
addToCartBtn.IsVisible(),
"دکمه 'افزودن به سبد' باید روی صفحه محصول نمایش داشته باشد"
)
Log.Message("✓ صفحه محصول باز شد: " + firstProductName)
# ---- مرحله ۳: افزودن به سبد ----
addToCartBtn.Touch()
aqUtils.Delay(800)
# Checkpoint: snackbar یا toast تأیید نمایش داده شود
confirmToast = app.FindElement("*", "text", "به سبد اضافه شد", 2000)
if confirmToast:
Log.Message("✓ پیام تأیید افزودن به سبد نمایش داده شد")
else:
Log.Warning("پیام تأیید یافت نشد — ممکن است سریع محو شده باشد")
# بررسی badge سبد خرید
cartIcon = app.FindElement("HomePage.CartIcon")
cartBadge = cartIcon.FindChild("cart_badge_text")
aqVerification.IsTrue(
int(cartBadge.Text) >= 1,
"badge سبد خرید باید حداقل ۱ نشان دهد"
)
Log.Message("✓ badge سبد خرید: " + cartBadge.Text)
گام ششم: Gesture های موبایل — Swipe، Scroll، و Long Press
تست موبایل بدون gesture testing ناقص است. TestComplete تمام gesture های استاندارد را پشتیبانی میکند:
def Test_ProductGallerySwipe():
"""
تست swipe روی گالری تصاویر محصول
"""
device = Mobile.Device("ArinShop_TestDevice")
app = device.Application("com.arinshop.android")
# رفتن به صفحه یک محصول خاص
NavigateToProduct(app, product_id=101)
aqUtils.Delay(1000)
gallery = app.FindElement("ProductPage.ImageGallery")
# ثبت ایندکس تصویر فعلی قبل از swipe
initialIndex = gallery.FindChild("image_indicator").Text # مثلاً "1/5"
# Swipe به چپ برای رفتن به تصویر بعدی
gallery.Swipe(
startX=0.8, # ۸۰٪ از عرض از چپ
startY=0.5, # وسط ارتفاع
endX=0.2, # ۲۰٪ از عرض از چپ
endY=0.5,
duration=300 # میلیثانیه
)
aqUtils.Delay(500)
afterSwipeIndex = gallery.FindChild("image_indicator").Text # باید "2/5" شود
aqVerification.CheckNotEqual(
initialIndex,
afterSwipeIndex,
"بعد از swipe، indicator گالری باید تغییر کند"
)
Log.Message("✓ Swipe موفق: از " + initialIndex + " به " + afterSwipeIndex)
def Test_ProductListScroll():
"""
تست scroll در لیست محصولات و بارگذاری lazy load
"""
device = Mobile.Device("ArinShop_TestDevice")
app = device.Application("com.arinshop.android")
productList = app.FindElement("HomePage.ProductList")
# شمارش آیتمهای قبل از scroll
countBefore = productList.ChildCount
Log.Message("آیتم قبل از scroll: " + str(countBefore))
# Scroll به پایین لیست
productList.ScrollDown(speed=500, distance=3000)
aqUtils.Delay(1500) # صبر برای lazy load
countAfter = productList.ChildCount
Log.Message("آیتم بعد از scroll: " + str(countAfter))
aqVerification.IsTrue(
countAfter >= countBefore,
"بعد از scroll به پایین، تعداد آیتمها نباید کاهش یابد"
)
def Test_LongPressForWishlist():
"""
تست Long Press برای اضافه کردن به علاقهمندیها
"""
device = Mobile.Device("ArinShop_TestDevice")
app = device.Application("com.arinshop.android")
productList = app.FindElement("HomePage.ProductList")
firstItem = productList.Child(0)
# Long Press (نگه داشتن ۸۰۰ میلیثانیه)
firstItem.TouchAndHold(duration=800)
aqUtils.Delay(500)
# منوی context باید ظاهر شود
contextMenu = app.FindElement("*", "resource-id",
"com.arinshop.android:id/context_menu", 1500)
aqVerification.IsTrue(
contextMenu is not None and contextMenu.IsVisible(),
"Long Press باید context menu را نمایش دهد"
)
# انتخاب گزینه «افزودن به علاقهمندی»
wishlistOption = contextMenu.FindChild("menu_add_wishlist")
wishlistOption.Touch()
Log.Message("✓ Long Press و افزودن به علاقهمندی موفق بود")
💬 برای دریافت قیمت و مشاوره رایگان → همین الان در تلگرام پیام دهید — پاسخ در کمتر از چند ساعت
گام هفتم: تست چرخش صفحه و Responsive Layout
اپلیکیشنهای موبایل باید در هر دو جهت portrait و landscape درست نمایش داده شوند:
def Test_OrientationChange():
"""
تست اینکه layout اپلیکیشن بعد از چرخش صفحه صحیح است
"""
device = Mobile.Device("ArinShop_TestDevice")
app = device.Application("com.arinshop.android")
# ---- حالت Portrait (پیشفرض) ----
device.Orientation = "Portrait"
aqUtils.Delay(800)
# بررسی نمایش صفحه اصلی در Portrait
searchBar = app.FindElement("HomePage.SearchBar")
aqVerification.IsTrue(
searchBar.IsVisible(),
"نوار جستجو در حالت Portrait باید نمایش داشته باشد"
)
# Region Checkpoint از صفحه در Portrait
Regions.Check("HomePage_Portrait", device.ScreenCapture())
# ---- تغییر به Landscape ----
device.Orientation = "Landscape"
aqUtils.Delay(1000) # صبر برای انیمیشن چرخش
# اپلیکیشن نباید crash کند
aqVerification.IsTrue(
app.IsRunning(),
"اپلیکیشن نباید بعد از چرخش صفحه crash کند"
)
# عناصر اصلی همچنان باید نمایش داشته باشند
aqVerification.IsTrue(
searchBar.IsVisible(),
"نوار جستجو در حالت Landscape باید نمایش داشته باشد"
)
# Region Checkpoint از صفحه در Landscape
Regions.Check("HomePage_Landscape", device.ScreenCapture())
Log.Message("✓ چرخش صفحه بدون مشکل انجام شد")
# برگشت به Portrait
device.Orientation = "Portrait"
aqUtils.Delay(500)
گام هشتم: مدیریت اعلانهای سیستمی
یکی از چالشهای خاص تست موبایل، dialog های سیستمی است — درخواست دسترسی به موقعیت مکانی، اعلانها، دوربین، و غیره. باید در اولین اجرای تست این موارد را مدیریت کنید:
def HandleSystemPermissions(app):
"""
تابع کمکی برای مدیریت dialog های دسترسی
باید قبل از هر تست فراخوانی شود
"""
# لیست متنهایی که در دکمه Allow/تأیید ممکن است باشد
allow_texts = ["Allow", "اجازه دادن", "OK", "تأیید", "While using the app"]
# حداکثر ۵ بار تلاش کن
for attempt in range(5):
dialog_found = False
for allow_text in allow_texts:
allow_btn = app.FindElement(
"*", "text", allow_text, timeout=1000
)
if allow_btn and allow_btn.IsVisible():
allow_btn.Touch()
aqUtils.Delay(500)
dialog_found = True
Log.Message("✓ دسترسی تأیید شد: " + allow_text)
break
if not dialog_found:
break # دیگر dialog ای نیست
def Test_FirstLaunchFlow():
"""
تست اولین اجرای اپلیکیشن — با مدیریت همه dialog های دسترسی
"""
device = Mobile.Device("ArinShop_TestDevice")
# پاک کردن داده اپلیکیشن برای شبیهسازی نصب تازه
device.ClearApplicationData("com.arinshop.android")
app = device.Application("com.arinshop.android")
app.Launch()
aqUtils.Delay(2000)
# مدیریت dialog های دسترسی
HandleSystemPermissions(app)
# صفحه onboarding باید نمایش داشته باشد
onboardingScreen = app.FindElement(
"*", "resource-id",
"com.arinshop.android:id/onboarding_container", 3000
)
aqVerification.IsTrue(
onboardingScreen is not None,
"صفحه onboarding برای کاربر جدید باید نمایش داده شود"
)
Log.Message("✓ جریان اولین اجرا صحیح است")
گام نهم: تست روی iOS
برای تست iOS، نیازمندیهای اضافی وجود دارد:
نیازمندیهای اجباری:
- یک Mac با Xcode نصبشده (برای iOS Simulator)
- برای دستگاه واقعی: Apple Developer Account و certificate
- TestComplete باید روی Windows باشد، اما میتواند از طریق شبکه به Mac متصل شود
تنظیم iOS Simulator
- در TestComplete از منوی Tools → Mobile گزینه Connect to Mac را انتخاب کنید
- آدرس Mac خود را وارد کنید
- بعد از اتصال، لیست simulator های موجود نمایش داده میشود
تفاوتهای کد برای iOS
اکثر کدهایی که برای Android نوشتیم با تغییرات جزئی برای iOS هم کار میکنند:
def Test_iOS_AddToCart():
"""
همان تست افزودن به سبد — اما روی iOS Simulator
"""
# تنها تفاوت: نوع دستگاه
device = Mobile.Device("iPhone_15_Simulator")
device.Activate()
app = device.Application("com.arinshop.ios")
app.Launch()
aqUtils.Delay(2500) # iOS معمولاً کمی دیرتر load میکند
# در iOS از Accessibility Identifier به جای resource-id استفاده میشود
searchBar = app.FindElement(
"*", "accessibilityIdentifier", "SearchBarInput"
)
searchBar.Touch()
searchBar.SetText("کفش ورزشی")
# در iOS برای ارسال جستجو باید روی کلید Search در کیبورد بزنیم
app.FindElement("*", "text", "Search").Touch()
aqUtils.Delay(1500)
productList = app.FindElement(
"*", "accessibilityIdentifier", "ProductCollectionView"
)
aqVerification.IsTrue(
productList.IsVisible(),
"لیست محصولات در iOS باید نمایش داشته باشد"
)
# ادامه تست مشابه Android است...
Log.Message("✓ تست iOS موفق بود")
گام دهم: Cross-Platform Testing — یک تست برای هر دو پلتفرم
برای کاهش تکرار کد، یک helper function مینویسیم که تست را با پارامتر پلتفرم اجرا میکند:
# ---- تعریف locator های هر پلتفرم ----
LOCATORS = {
"android": {
"search_bar": ("resource-id", "com.arinshop.android:id/search_input"),
"product_list": ("resource-id", "com.arinshop.android:id/product_recycler"),
"add_to_cart": ("resource-id", "com.arinshop.android:id/btn_add_to_cart"),
"cart_badge": ("resource-id", "com.arinshop.android:id/cart_badge_text"),
"checkout_btn": ("resource-id", "com.arinshop.android:id/btn_checkout"),
},
"ios": {
"search_bar": ("accessibilityIdentifier", "SearchBarInput"),
"product_list": ("accessibilityIdentifier", "ProductCollectionView"),
"add_to_cart": ("accessibilityIdentifier", "AddToCartButton"),
"cart_badge": ("accessibilityIdentifier", "CartBadgeLabel"),
"checkout_btn": ("accessibilityIdentifier", "CheckoutButton"),
}
}
DEVICES = {
"android": {"name": "ArinShop_Android", "app_id": "com.arinshop.android"},
"ios": {"name": "iPhone_15_Simulator","app_id": "com.arinshop.ios"},
}
def RunPurchaseFlow(platform):
"""
اجرای کامل فرآیند خرید روی هر دو پلتفرم
"""
cfg = DEVICES[platform]
loc = LOCATORS[platform]
device = Mobile.Device(cfg["name"])
device.Activate()
app = device.Application(cfg["app_id"])
app.Launch()
aqUtils.Delay(2000)
Log.AppendFolder("خرید روی " + platform.upper())
# جستجو
searchBar = app.FindElement(*loc["search_bar"])
searchBar.Touch()
searchBar.SetText("کیف چرم")
device.Keys("[Enter]")
aqUtils.Delay(1500)
# انتخاب اولین محصول
productList = app.FindElement(*loc["product_list"])
productList.Child(0).Touch()
aqUtils.Delay(1000)
# افزودن به سبد
addBtn = app.FindElement(*loc["add_to_cart"])
addBtn.Touch()
aqUtils.Delay(800)
# بررسی badge
badge = app.FindElement(*loc["cart_badge"])
aqVerification.CheckEqual(
badge.Text, "1",
platform + ": badge سبد خرید باید ۱ باشد"
)
Log.Message("✓ " + platform.upper() + ": فرآیند خرید موفق")
Log.PopLogFolder()
def Test_CrossPlatform_PurchaseFlow():
"""
اجرای تست خرید روی Android و iOS به ترتیب
"""
RunPurchaseFlow("android")
RunPurchaseFlow("ios")
Log.Message("✓ تست cross-platform با موفقیت انجام شد")
گام یازدهم: Data-Driven Mobile Testing
درست مثل تست API، میتوانید تست موبایل را هم data-driven کنید. این برای تست جستجو با کلمات مختلف یا تست ورود با اکانتهای متفاوت بسیار مفید است:
def Test_DataDriven_Search():
"""
تست جستجو با کلمات مختلف از فایل Excel
"""
device = Mobile.Device("ArinShop_TestDevice")
app = device.Application("com.arinshop.android")
DDT.ExcelDriver(
"C:\\TestData\\mobile_search_terms.xlsx",
"Sheet1",
True
)
while not DDT.CurrentDriver.EOF():
search_term = DDT.CurrentDriver.Value("search_term")
min_results = int(DDT.CurrentDriver.Value("min_expected_results"))
should_find = DDT.CurrentDriver.Value("should_find") == "TRUE"
Log.AppendFolder("جستجو: " + search_term)
# برگشتن به صفحه اصلی قبل از هر جستجو
app.PressKey("Back")
app.Launch()
aqUtils.Delay(1000)
# انجام جستجو
searchBar = app.FindElement(
"resource-id", "com.arinshop.android:id/search_input"
)
searchBar.Touch()
searchBar.SetText(search_term)
device.Keys("[Enter]")
aqUtils.Delay(1500)
productList = app.FindElement(
"resource-id", "com.arinshop.android:id/product_recycler"
)
if should_find:
aqVerification.IsTrue(
productList.ChildCount >= min_results,
"جستجوی '" + search_term + "' باید حداقل " +
str(min_results) + " نتیجه داشته باشد"
)
else:
emptyState = app.FindElement(
"resource-id",
"com.arinshop.android:id/empty_search_state",
2000
)
aqVerification.IsTrue(
emptyState is not None,
"جستجوی '" + search_term + "' باید نمایش 'نتیجهای یافت نشد' داشته باشد"
)
Log.PopLogFolder()
DDT.CurrentDriver.Next()
DDT.CurrentDriver.Close()
💬 برای دریافت قیمت و مشاوره رایگان → همین الان در تلگرام پیام دهید — پاسخ در کمتر از چند ساعت
گام دوازدهم: یکپارچگی با CI/CD
اجرای تستهای موبایل در pipeline یک چالش اضافه دارد: باید دستگاه یا emulator همیشه در دسترس باشد.
راهحل پیشنهادی: یک ماشین Windows که دائماً روشن است، با یک emulator Android که در پسزمینه اجرا میشود. CI/CD هر بار این ماشین را فراخوانی میکند.
# .gitlab-ci.yml
stages:
- mobile-test
android-regression:
stage: mobile-test
tags:
- windows-mobile-runner # Runner مخصوص که به دستگاه Android دسترسی دارد
script:
- |
# راهاندازی emulator قبل از تست (اگر دستگاه واقعی نیست)
& "C:\Android\emulator\emulator.exe" -avd Pixel_6_API_33 -no-window &
Start-Sleep -Seconds 30
# اجرای تستهای TestComplete
& "C:\Program Files\SmartBear\TestComplete 15\Bin\TestComplete.exe" `
"ArinShopMobileTests\ArinShopMobileTests.pjs" `
/run /TestItem:"AndroidTests" `
/exit /SilentMode `
/ExportLog:"TestResults\android-results.xml"
artifacts:
reports:
junit: TestResults/android-results.xml
paths:
- TestResults/
only:
- main
- release/*
نتایج واقعی: چه تغییری کرد؟
بعد از دو ماه استفاده از TestComplete Mobile، تیم آرینشاپ این نتایج را گزارش داد:
| معیار | قبل | بعد |
|---|---|---|
| زمان تست موبایل قبل از release | ۱۶ ساعت (دستی) | ۳ ساعت (خودکار) |
| تعداد دستگاههای تستشده | ۳ دستگاه | ۵ دستگاه + ۲ emulator |
| پوشش سناریو | ۴۵ سناریو | ۱۱۰ سناریو |
| باگهای orientation-specific | اغلب کشف نمیشدند | ۱۰۰٪ شناسایی |
| تست cross-platform | هر release دستی | هر commit خودکار |
مهمتر از اعداد: تیم توانست با اطمینان اپلیکیشن را برای پشتیبانی از Android 14 بهروزرسانی کند — چون میدانست ۱۱۰ تست خودکار هر regression را پیدا میکنند.
بهترین شیوهها
همیشه با aqUtils.Delay صبور باشید
اپلیکیشنهای موبایل نسبت به وب به زمان بیشتری برای انیمیشن و load نیاز دارند. به جای delay های ثابت، از wait شرطی استفاده کنید:
def WaitForElement(app, locator_type, locator_value, timeout_ms=5000):
"""
منتظر میماند تا عنصر نمایش داده شود
به جای delay ثابت
"""
start = aqUtils.GetTickCount()
while aqUtils.GetTickCount() - start < timeout_ms:
el = app.FindElement("*", locator_type, locator_value, 500)
if el and el.IsVisible():
return el
aqUtils.Delay(200)
Log.Warning("عنصر در " + str(timeout_ms) + "ms یافت نشد: " + locator_value)
return None
پیش از هر تست، حالت دستگاه را ریست کنید
def SetupMobileTest(device_name, app_id):
"""
آمادهسازی استاندارد قبل از هر تست موبایل
"""
device = Mobile.Device(device_name)
device.Activate()
device.Orientation = "Portrait" # همیشه از Portrait شروع کن
app = device.Application(app_id)
app.Terminate() # بستن اجرای قبلی
aqUtils.Delay(500)
app.Launch()
aqUtils.Delay(2000)
return device, app
از resource-id به جای text برای locator استفاده کنید
متن رابط کاربری در اپلیکیشنهای چندزبانه تغییر میکند. resource-id پایدارتر است و به ترجمه حساس نیست.
تستهای gesture را با سرعتهای مختلف اجرا کنید
رفتار swipe روی دستگاههای مختلف متفاوت است. با duration پارامتر بازی کنید — گاهی یک swipe خیلی سریع توسط اپلیکیشن تشخیص داده نمیشود.
جمعبندی
تست موبایل با TestComplete یک اتفاق بزرگ برای تیمهایی است که قبلاً از ابزار جداگانه برای هر پلتفرم استفاده میکردند. یکپارچگی با تستهای وب و API در یک پروژه TestComplete یعنی میتوانید یک end-to-end test کامل داشته باشید — از API تا رابط کاربری موبایل — همه در یک گزارش.
برای خرید لایسنس TestComplete با ماژول Mobile یا مشاوره رایگان درباره راهاندازی تست موبایل برای پروژهتان، از طریق تلگرام با ما در تماس باشید.
سوالات متداول
سوال: آیا TestComplete روی emulator Android هم کار میکند؟
بله. TestComplete با هر emulator که ADB آن را شناسایی کند کار میکند — Android Studio AVD Manager، Genymotion، یا BlueStacks. تنها تفاوت در سرعت اجرا است؛ دستگاه واقعی همیشه قابلاعتمادتر است.
سوال: آیا میتوان اپلیکیشنهای Flutter یا React Native را تست کرد؟
React Native: بله، چون در نهایت native components رندر میکند که UI Automator میتواند آنها را شناسایی کند. Flutter: چالشبرانگیزتر است چون Flutter عناصر خودش را رندر میکند و مستقیماً در درخت accessibility قرار نمیگیرند. برای Flutter توصیه میشود از Accessibility labels در کد اپلیکیشن استفاده کنید.
سوال: تفاوت تست روی دستگاه واقعی و emulator چیست؟
دستگاه واقعی: قابلاعتمادتر، رفتار gesture دقیقتر، تست sensor ها ممکن، اما گرانتر و نیاز به نگهداری دارد. Emulator: ارزانتر، راحتتر در CI/CD، اما ممکن است رفتار برخی feature های سختافزاری را شبیهسازی نکند.
سوال: چطور میتوانم screenshot خودکار در هر مرحله بگیرم؟
TestComplete در هر بار شکست تست به صورت خودکار screenshot میگیرد. برای screenshot دستی در طول تست از device.ScreenCapture() استفاده کنید و نتیجه را با Log.Picture() در گزارش ذخیره کنید.
سوال: آیا میتوان تستهای موبایل را به صورت موازی روی چند دستگاه اجرا کرد؟
بله، با ماژول Distributed Testing در TestComplete میتوانید تست را روی چند دستگاه متصل به صورت همزمان اجرا کنید. هر دستگاه یک slave است که تست یکسان یا متفاوت اجرا میکند.
سوال: چطور network traffic اپلیکیشن موبایل را در تست مانیتور کنم؟
TestComplete به تنهایی network monitoring ندارد. میتوانید TestComplete را با یک proxy مثل Charles Proxy ترکیب کنید — دستگاه را به proxy متصل کنید و همزمان تست را اجرا کنید.
خرید لایسنس اورجینال — مشاوره رایگان
قیمت دقیق بر اساس نسخه و تعداد کاربر متفاوت است. برای دریافت قیمت و راهنمایی رایگان با ما در تلگرام پیام دهید.
|
✓
+۲۰ سال تجربه
متخصصان مهندسی نرمافزار با سابقه بلندمدت
|
⚡
تحویل زیر ۲۴ ساعت
لایسنس شما ظرف یک روز کاری ارسال میشود
|
↩
ضمانت بازگشت وجه
در صورت عدم کارایی، مبلغ را کامل برمیگردانیم
|
پاسخ معمولاً در کمتر از چند ساعت — بدون پیشپرداخت برای مشاوره



