首页 >> 通信 >> 干货 | 一文搞定 uiautomator2 自动化测试工具用于

干货 | 一文搞定 uiautomator2 自动化测试工具用于

2023-02-28 通信

ent.count)

4.2 拥护的出发点方式为

ui2 拥护 android 当中 UiSelector 类当中的所有出发点方式为,详尽可以在这个网址提示

整体主旨如下 , 所有的类型可以通过 weditor 提示到

英文名称描述texttext 是选定评注的成份textContainstext 当中涵盖有选定评注的成份textMatchestext 相一致选定同调的成份textStartsWithtext 以选定评注开头的成份classNameclassName 是选定类名的成份classNameMatchesclassName 类名相一致选定同调的成份descriptiondescription 是选定评注的成份descriptionContainsdescription 当中涵盖有选定评注的成份descriptionMatchesdescription 相一致选定同调的成份descriptionStartsWithdescription 以选定评注开头的成份checkable可健康检查的成份,表达式为 True,Falsechecked已选当中的成份,通常使用复选框,表达式为 True,Falseclickable可点击的成份,表达式为 True,FalselongClickable可稍长按的成份,表达式为 True,Falsescrollable可回转的成份,表达式为 True,Falseenabled已激活的成份,表达式为 True,Falsefocusable可聚焦的成份,表达式为 True,Falsefocused获得了焦点的成份,表达式为 True,Falseselected也就是说选当中的成份,表达式为 True,FalsepackageNamepackageName 为选定包名的成份packageNameMatchespackageName 为相一致同调的成份resourceIdresourceId 为选定主旨的成份resourceIdMatchesresourceId 为相一致选定同调的成份

4.3 子成份和两兄弟出发点

子成份出发点

child()

#索引类名为 android.widget.ListView 下的 Bluetooth 成份

d(className="android.widget.ListView").child(text="Bluetooth")

# 请注意这两种方式为出发点有点不可靠,不建议常用

d(className="android.widget.ListView")

.child_by_text("Bluetooth",allow_scroll_search=True)

d(className="android.widget.ListView").child_by_description("Bluetooth")

两兄弟成份出发点

sibling()

#索引与 google 同一级别,类名为 android.widget.ImageView 的成份

d(text="Google").sibling(className="android.widget.ImageView")

链式调用

d(className="android.widget.ListView", resourceId="android:id/list")

.child_by_text("Wi‑Fi", className="android.widget.LinearLayout")

.child(className="android.widget.Switch")

.click()

4.4 相对出发点

相对出发点拥护在left, right, top, bottom, 即在某个成份的前后左右

d(A).left(B),# 选择 A 上方的 B

d(A).right(B),# 选择 A 后面的 B

d(A).up(B), #选择 A 上边的 B

d(A).down(B),# 选择 A 下边的 B

#选择 WIFI 后面的开关按钮

d(text='Wi‑Fi').right(resourceId='android:id/widget_frame')

4.5 成份常用 API

表格注明有 @property 装饰物的类类型分析方法,仅为上方示例方式为

d(test="Settings").exists

分析方法

描述来到取值备注exists()判断成份是否假定True,Flase@propertyinfo()来到成份的所有文档字典@propertyget_text()来到成份评注字符串

set_text(text)分设成份评注None

clear_text()搬走成份评注None

center()来到成份的正上方所在位置(x,y)基于整个中门幕的点

exists 其它常用分析方法:

d.exists(text='Wi‑Fi',timeout=5)

info() 负载文档:

{

"bounds": {

"bottom": 407,

"left": 216,

"right": 323,

"top": 342

},

"childCount": 0,

"className": "android.widget.TextView",

"contentDescription": null,

"packageName": "com.android.settings",

"resourceName": "android:id/title",

"text": "Wi‑Fi",

"visibleBounds": {

"bottom": 407,

"left": 216,

"right": 323,

"top": 342

},

"checkable": false,

"checked": false,

"clickable": false,

"enabled": true,

"focusable": false,

"focused": false,

"longClickable": false,

"scrollable": false,

"selected": false

}

可以通过上方文档分别利用成份的所有类型

4.6 XPATH 出发点

因为 Java uiautoamtor 当中可选是不拥护 xpath,这是属于 ui2 的拓展机制,速度但会相对其它出发点方式为迟一些

在 xpath 出发点当中,ui2 当中的 description 出发点须要代替为 content-desc,resourceId 须要代替为 resource-id

常用分析方法

# 只但会来到一个成份,如果找不到成份,则但会报 XPathElementNotFoundError 缺失

# 如果找多个成份,可选但会来到第 0 个

d.xpath('//*[@resource-id="com.android.launcher3:id/icon"]')

# 如果来到的成份有多个,须要常用 all() 分析方法来到本表

# 常用 all 分析方法,当未找成份时,不但会报错,但会来到一个空本表

d.xpath('//*[@resource-id="com.android.launcher3:id/icon"]').all()

五、电子元件交互

5.1 机器右方

d(text='Settings').click()

#机器右方直到成份绝迹 , 了事时间 10,点击间隔 1

d(text='Settings').click_gone(maxretry=10, interval=1.0)

5.2 稍长按

d(text='Settings').long_click()

5.3 拖拽

Android

# 在 0.25S 内将 Setting 拖拽至 Clock 上,拖拽成份的当其中心所在位置

# duration 可选为 0.5, 确实拖拽的时间但会比分设的要颇高

d(text="Settings").drag_to(text="Clock", duration=0.25)

# 拖拽 settings 到中门幕的某个点上

d(text="Settings").drag_to(877,733, duration=0.25)

#两个点之间的拖拽 , 从点 1 拖拽至点 2

d.drag(x1,y1,x2,y2)

5.4 转动

转动有两个,一个是在 driver 上可用,一个是在成份上可用

成份上可用

从成份的当其中心向成份边缘转动

# 在 Setings 上向上转动。steps 可选为 10

# 1 步约为 5 毫秒,因此 20 步约为 0.1 s

d(text="Settings").swipe("up", steps=20)

driver 上可用

即对整个中门幕可用

# 充分利用下滑可用

x,y = d.window_size()

x1 = x / 2

y1 = y * 0.1

y2 = y * 0.9

d.swipe(x1,y1,x1,y2)

driver 转动的拓展分析方法,可以必要充分利用转动,不须要再自己元件出发点点

# 拥护前后左右的转动

# "left", "right", "up", "down"

# 下滑可用

d.swipe_ext("down")

5.5 双指可用

android>4.3

对成份可用

d(text='Settings').gesture(start1,start2,end1,end2,)

# 缩放可用

d(text='Settings').gesture((525,960),(613,1121),(135,622),(882,1540))

元件好的缩放缩小可用

# 缩小

d(text="Settings").pinch_in()

# 缩放

d(text="Settings").pinch_out()

5.6 准备好成份显现出或者绝迹

# 准备好成份显现出

d(text="Settings").wait(timeout=3.0)

# 准备好成份绝迹,来到 True False,timout 可选为一个系统分设的准备好时间

d(text='Settings').wait_gone(timeout=20)

5.7 回转GUI

分设 scrollable 类型为 True;

回转多种类型:horiz 为水平,vert 为纵向;

回转方向:

forward 向下 backward 向后 toBeginning 回转至开始 toEnd 回转至最后 to 回转必要某个成份显现出

所有分析方法仅来到 Bool 取值;

# 纵向回转到的网站顶部 / 纵向回转到最上方

d(scrollable=True).scroll.toBeginning()

d(scrollable=True).scroll.horiz.toBeginning()

# 纵向回转到的网站最底部 / 纵向回转到最左方

d(scrollable=True).scroll.toEnd()

d(scrollable=True).scroll.horiz.toEnd()

# 纵向向后回转到指出发点置 / 纵向向右回转到指出发点置

d(scrollable=True).scroll.to(description=" 指出发点置 ")

d(scrollable=True).scroll.horiz.to(description=" 指出发点置 ")

# 纵向向下回转(纵向并不一定)

d(scrollable=True).scroll.forward()

# 纵向向下回转到指出发点置(纵向并不一定)

d(scrollable=True).scroll.forward.to(description=" 指出发点置 ")

# 回转直到 System 成份显现出

d(scrollable=True).scroll.to(text="System")

Take screenshot of widget

im = d(text="Settings").screenshot()

im.save("settings.jpg")

5.8 匹配

5.8.1 匹配自定义评注

# 常用 adb 广播的方式为匹配

d.send_keys('hello')

# 搬走匹配框

d.clear_text()

5.8.2 匹配按键

两种分析方法

# 发送回车

d.press('enter')

# 第二种

d.keyevent('enter')

以外 press 拥护的按键如下

"""

press key via name or key code. Supported key name includes:

home, back, left, right, up, down, center, menu, search, enter,

delete(or del), recent(recent apps), volume_up, volume_down,

volume_mute, camera, power.

"""

keyevent 是通过 “adb shell input keyevent” 方式为匹配,拥护按键更加加丰富

更加多详尽的按键文档

5.8.3 匹配法切换

# 切换成 ui2 的匹配法,这里但会隐匿干脆系统设计本来的匹配法 , 可选是常用系统设计匹配法

# 当传入 False 时但会常用系统设计可选匹配法,可选为 Fasle

d.set_fastinput_ime(True)

# 提示也就是说匹配法

d.current_ime()

#来到取值

('com.github.uiautomator/.FastInputIME', True)

5.8.4 虚拟匹配法机制

可以虚拟的机制有 go ,search ,send ,next, done ,previous。

如果常用 press 匹配按键无效,可以设法常用此分析方法匹配

# 追踪机制

d.send_action("search")

5.9 toast 可用

# 利用 toast, 当没有找 toast 消息时,来到 default 主旨

d.toast.get_message(timout=5,default='no toast')

# 搬走 toast 内存

d.toast.reset()

5.10 监视系统设计GUI

常用 wather 进行时GUI的监视系统设计,可以用来充分利用省去测试者过程当中的弹框

当开启 wather 时,但会建成一个终点站程进行时监视系统设计

可以加到多个 watcher

用法

# 注册监视系统设计 , 当GUI内显现出有 allow 标有时,点击 allow

d.watcher.when('allow').click()

# 移去 allow 的监视系统设计

d.watcher.remove("allow")

# 移去所有的监视系统设计

d.watcher.remove()

# 开始后台监视系统设计

d.watcher.start()

d.watcher.start(2.0) # 可选监视系统设计间隔 2.0s

# 强制行驶所有监视系统设计

d.watcher.run()

# 终止监视系统设计

d.watcher.stop()

# 终止并移去所有的监视系统设计,常使用调用

d.watcher.reset()

2.11.0 旧版 新增了一个 watch_context 分析方法 , 发音相对 watcher 更加简洁,官方破例常用此分析方法来充分利用监视系统设计,以外只拥护 click() 这一种分析方法。

wct = d.watch_context()

# 监视系统设计 ALLOW

wct.when("ALLOW").click()

# 监视系统设计 OK

wct.when('OK').click()

# 掀开弹窗监视系统设计,并准备好GUI稳定(两个弹窗健康检查周期内没有弹窗都有稳定)

wct.wait_stable()

#其它充分利用预定义

# 终止监视系统设计

wct.stop()

5.11 该系统转动

这里可以用来充分利用标志破关

常用 touch 类

# 虚拟按下不放手

touch.down(x,y)

# 停住 3S

touch.sleep(x,y)

# 虚拟旋转

touch.move(x,y)

# 虚拟放开

touch.up(x,y)

#充分利用稍长按 , 同一个点按下休眠 5S 后把手

d.touch.down(252,1151).sleep(5).up(252,1151)

# 充分利用四点的标志破关,以外只拥护坐标点

d.touch.down(252,1151).move(559,1431).move(804,1674).move(558,1666).up(558,1666)

六、图像可用

6.1 截图

d.screenshot('test.png')

6.2 伴唱片段

这个感觉是比较贫乏于的一个机制,可以在测试者用例开始时伴唱,结束时终止伴唱,然后如果测试者 fail。则上传来测试者报告,完美复原可用现场,具体原理后面再去深入研究。

首先须要串流贫乏,官方破例常用镜像串流:

pip3 install -U "uiautomator2[image]" -i

执行者伴唱:

# 开启伴唱,可选帧率为 20

d.screenrecord('test.mp4')

# 其它可用

time.sleep(10)

#终止伴唱,只有终止伴唱了才能碰到片段

d.screenrecord.stop()

6.3 图片识别点击

串流与伴唱片段同一套贫乏。

这个机制是首先手动除此以外须要点击目标的图片,然后 ui2 在GUI当中去意味着这个图片,以外我设法了精确试不是很颇高,超载率非常颇高,不建议常用。

# 点击

d.image.click('test.png')

# 意味着图片,来到相似度和坐标

# {'similarity': 0.9314796328544617, 'point': [99, 630]}

d.image.match('test.png')

七、应用领域管理者

7.1 利用也就是说GUI的 APP 文档

d.app_current()

#来到也就是说GUI的包名,activity 及 pid

{

"package": "com.xueqiu.android",

"activity": ".common.MainActivity",

"pid": 23007

}

7.2 装设应用领域

可以从本地路径及 url 串流装设 APP,此分析方法无来到取值,当装设败北时,但会丢出 RuntimeError 短时间性

# 本地路径装设

d.app_install('test.apk')

# url 装设

d.app_install('')

7.3 行驶应用领域

可选当应用领域在行驶稍长时间执行者 start 时不但会终止使用应用领域,而是在此之后保持也就是说GUI。

如果须要抑止前面的开启稍长时间,则须要加 stop=True 表达式。

# 通过包名开启

d.app_start("com.xueqiu.android",stop=True)

#源编码说明

def app_start(self, package_name: str,

activity: Optional[str]=None,

wait: bool = False,

stop: bool=False,

use_monkey: bool=False):

""" Launch application

Args:

package_name (str): package name

activity (str): app activity

stop (bool): Stop app before starting the activity. (require activity)

use_monkey (bool): use monkey command to start app when activity is not given

wait (bool): wait until app started. default False

"""

7.4 终止应用领域

stop 和 clear 的区别是结束应用领域常用的号令不同

stop 常用的是 “am force-stop”

clear 常用的是 “pm clear”

# 通过包名结束单个应用领域

d.app_stop("com.xueqiu.android")

d.app_clear('com.xueqiu.android')

# 结束所有应用领域 , 除了 excludes 表达式本表当中的应用领域包名

# 如果不传参,则但会只保留两个贫乏公共服务应用领域

# 但会来到一个结束应用领域的包名本表

d.app_stop_all(excludes=['com.xueqiu.android'])

7.5 利用应用领域文档

d.app_info('com.xueqiu.android')

#负载

{

"packageName": "com.xueqiu.android",

"mainActivity": "com.xueqiu.android.common.splash.SplashActivity",

"label": " 雪球 ",

"versionName": "12.6.1",

"versionCode": 257,

"size": 72597243

}

7.6 利用应用领域图标

img = d.app_icon('com.xueqiu.android')

img.save('icon.png')

7.7 准备好应用领域开启

# 准备好此应用领域变为也就是说应用领域,来到 pid,了事未开启设法则来到 0

# front 为 true 回应准备好 app 成为也就是说 app,

# 可选为 false,回应只要后台有这个应用领域的信息流就但会来到 PID

d.app_wait('com.xueqiu.android',60,front=True)

7.8 自带应用领域

# 自带设法来到 true, 没有此包或者自带败北来到 False

d.app_uninstall('com.xueqiu.android')

# 自带所有自己装设的第三方应用领域 , 来到自带 app 的包名本表

# excludes 回应不自带的本表

# verbose 为 true 则但会读取自带文档

d.app_uninstall_all(excludes=[],verbose=True)

自带全部应用领域来到的包名本表并一定是自带设法了,众所周知常用 verbose=true 读取一下文档,这样可以提示到是否自带设法

uninstalling com.xueqiu.android OK

uninstalling com.android.cts.verifier FAIL

或者可以改写一下源编码,使其只负载设法的包名,释义的为减少的预定义,未释义的是源编码

def app_uninstall_all(self, excludes=[], verbose=False):

""" Uninstall all apps """

our_apps = ['com.github.uiautomator', 'com.github.uiautomator.test']

output, _ = self.shell(['pm', 'list', 'packages', '-3'])

pkgs = re.findall(r'package:([^s]+)', output)

pkgs = set(pkgs).difference(our_apps + excludes)

pkgs = list(pkgs)

# 减少一个自带设法的本表

#sucess_list = []

for pkg_name in pkgs:

if verbose:

print("uninstalling", pkg_name, " ", end="", flush=True)

ok = self.app_uninstall(pkg_name)

if verbose:

print("OK" if ok else "FAIL")

# 减少如下语句,当设法则将包名转到 list

#if ok:

# sucess_list.append(pkg_name)

# 来到设法的本表

# return sucess_list

return pkgs

八、其它实用分析方法

8.1 连结电子元件

#当 PC 只连结了一个电子元件时,可以常用此种方式为

d = u2.connect()

#来到的是 Device 类 , 此类后继者方式为如下

class Device(_Device, _AppMixIn, _PluginMixIn, _InputMethodMixIn, _DeprecatedMixIn):

""" Device object """

# for compatible with old code

Session = Device

connect() 可以常用如下其它方式为进行时连结

#当 PC 与电子元件在同一子系统时,可以常用 IP 接收者和端口号通过 WIFI 连结,无需连结 USB 终点站

connect("10.0.0.1:7912")

connect("10.0.0.1") # use default 7912 port

connect("")

connect("")

#多个电子元件时,常用电子元件号选定哪一个电子元件

connect("cff1123ea") # adb device serial number

8.2 利用电子元件及 driver 文档

8.2.1 利用 driver 文档

d.info

#负载

{

"currentPackageName": "com.android.systemui",

"displayHeight": 2097,

"displayRotation": 0,

"displaySizeDpX": 360,

"displaySizeDpY": 780,

"displayWidth": 1080,

"productName": "freedom_turbo_XL",

"screenOn": true,

"sdkInt": 29,

"naturalOrientation": true

}

8.2.2 利用电子元件文档

但会负载测试者电子元件的所有文档,还包括电源,CPU,内存等

d.device_info

#负载

{

"udid": "61c90e6a-ba:1b:ba:46:91:0e-freedom_turbo_XL",

"version": "10",

"serial": "61c90e6a",

"brand": "Schok",

"model": "freedom turbo XL",

"hwaddr": "ba:1b:ba:46:91:0e",

"port": 7912,

"sdk": 29,

"agentVersion": "0.9.4",

"display": {

"width": 1080,

"height": 2340

},

"battery": {

"acPowered": false,

"usbPowered": true,

"wirelessPowered": false,

"status": 2,

"health": 2,

"present": true,

"level": 98,

"scale": 100,

"voltage": 4400,

"temperature": 292,

"technology": "Li-ion"

},

"memory": {

"total": 5795832,

"around": "6 GB"

},

"cpu": {

"cores": 8,

"hardware": "Qualcomm Technologies, Inc SDM665"

},

"arch": "",

"owner": null,

"presenceChangedAt": "0001-01-01T00:00:00Z",

"usingBeganAt": "0001-01-01T00:00:00Z",

"product": null,

"provider": null

}

8.2.3 利用中门幕解像度

# 来到(宽,颇高)个位

d.window_size()

# 例 解像度为 1080*1920

# APP竖中门稍长时间来到 (1080,1920)

# 横中门稍长时间来到 (1920,1080)

8.2.4 利用 IP 接收者

# 来到 ip 接收者字符串,如果没有则来到 None

d.wlan_ip

8.3 driver 一个系统分设

8.3.1 常用 settings 分设

提示 settings 可选分设

d.settings

#负载

{

#点击后的过稍长,(0,3)回应成份点击前准备好 0 秒,点击后准备好 3S 再执行者后续可用

'operation_delay': (0, 3),

# opretion_delay 施行的分析方法,可选为 click 和 swipe

# 可以减少 press,send_keys,long_click 等方式为

'operation_delay_methods': ['click', 'swipe'],

# 可选准备好时间,仅有 appium 的隐式准备好

'wait_timeout': 20.0,

# xpath 存档

'xpath_debug': False

}

改写可选分设,只须要改写 settings 字典即可

#改写过稍长为可用前过稍长 2S 可用后过稍长 4.5S

d.settings['operation_delay'] = (2,4.5)

#改写过稍长施行分析方法

d.settings['operation_delay_methods'] = {'click','press','send_keys'}

# 改写可选准备好

d.settings['wait_timeout'] = 10

8.3.2 常用分析方法或者类型分设

http 可选劝告了事时间

# 可选取值 60s,

d.HTTP_TIMEOUT = 60

当电子元件干脆终点站时,准备好电子元件在终点站时稍长

# 仅当 TMQ=true 时有效,拥护通过环境变量 WAIT_FOR_DEVICE_TIMEOUT 分设

d.WAIT_FOR_DEVICE_TIMEOUT = 70

成份索引可选准备好时间

# 打不到成份时,准备好 10 后再报短时间性

d.implicitly_wait(10.0)

打开 HTTP debug 文档

d.debug = True

d.info

#负载

15:52:04.736 $ curl -X POST -d '{"jsonrpc": "2.0", "id": "0eed6e063989e5844feba578399e6ff8", "method": "deviceInfo", "params": {}}' ''

15:52:04.816 Response (79 ms)>>>

{"jsonrpc":"2.0","id":"0eed6e063989e5844feba578399e6ff8","result":{"currentPackageName":"com.android.systemui","displayHeight":2097,"displayRotation":0,"displaySizeDpX":360,"displaySizeDpY":780,"displayWidth":1080,"productName":"freedom_turbo_XL","screenOn":true,"sdkInt":29,"naturalOrientation":true}}

休眠

# 仅有 time.sleep(10)

d.sleep(10)

8.4 亮灭中门

# 亮中门

d.screen_on()

# 灭中门

d.screen_off()

8.5 中门幕方向

# 分设中门幕方向

d.set_orientation(value)

# 利用也就是说中门幕方向

d.orientation

value 取值详见,只要是个位当中的任一一个取值就可以。

# 经常性竖中门

(0, "natural", "n", 0),

# 右方横中门,仅有APP中门幕顺时针旋转轴 90 度

# 现实当中如果要超过此效果,须要将APP逆时针旋转轴 90 度

(1, "left", "l", 90),

# 一个大,这个须要看APP系统设计是否拥护 , 倒过来显示

(2, "upsidedown", "u", 180),

# 左方横中门,变更加与右方相反,中门幕顺时针旋转轴 270 度

(3, "right", "r", 270))

8.6 打开通知右方与快速分设

打开通知右方

d.open_notification()

打开快速分设

d.open_quick_settings()

8.7 邮件导入等价

8.7.1 导入邮件

# 如果是目录,这里 "/sdcrad/" 最后一个斜杠一定要加,否则但会报错

d.push("test.txt","/sdcrad/")

d.push("test.txt","/sdcrad/test.txt")

8.7.2 等价邮件

d.pull('/sdcard/test.txt','text.txt')

8.8 执行者 shell 号令

常用 shell 分析方法执行者

8.8.1 执行者非漏出号令

output 来到的是一个整体的字符串,如果须要抽取取值,须要对 output 进行时类比提取处理

# 来到负载和退出编码,经常性为 0,短时间性为 1

output,exit_code = d.shell(["ls","-l"],timeout=60)

8.8.2 执行者漏出号令(短时间执行者的号令)

# 来到一个号令的信息流 output 为 requests.models.Response

output = d.shell('logcat',stream=True)

try:

# 按行读取,iter_lines 为迭代自发信息,一次一行

for line in output.iter_lines():

print(line.decode('utf8'))

finally:

output.close()

源编码描述

def shell(self, cmdargs: Union[str, List[str]], stream=False, timeout=60):

"""

Run adb shell command with arguments and return its output. Require atx-agent>=0.3.3

Args:

cmdargs: str or list, example: "ls -l" or ["ls", "-l"]

timeout: seconds of command run, works on when stream is False

stream: bool used for long running process.

Returns:

(output, exit_code) when stream is False

requests.Response when stream is True, you have to close it after using

Raises:

RuntimeError

For atx-agent is not support return exit code now.

When command got something wrong, exit_code is always 1, otherwise exit_code is always 0

"""

8.9 session(以外已经被弃用)

8.10 终止 UI2 公共服务

因为有 atx-agent 的假定,Uiautomator 但会被一直城主着,如果退出了就但会被重新开启起来。但是 Uiautomator 又是霸道的,一旦它在行驶,APP上的辅助机制、电脑上的 uiautomatorviewer 就都不可用了,除非关干脆该方法论本身的 uiautomator

常用预定义终止

d.service("uiautomator").stop()

手动终止

必要打开 ATX APP(init 设法后,就但会装设上),点击终止使用 UIAutomator

以上,欢迎大家一起交流揭示。

⬇️ 克隆“上方绑定”,降低测试者内部经济效益!

>>更加多技术评论交友和仅限资料申领

_id=qrcodePricefrom=souhuPricetimestamp=1650330644

河北干细胞治疗的医院
经常便秘怎么办呢
藿香正气口服液
早泄的是什么原因
脑中风后遗症的治疗费用
友情链接