传到网盘里分享一下
方便上传下那个 log_20181025_101740 文件夹吗?看你截图分析不出来啥问题
大牛不敢当,对你有帮助就好
看信息应该是图片没找到,你看看生成日志文件夹里有对应的图片吗?正常情况文件夹里应该包含图片和 log.txt
runner.py 改成下面的代码就不用封装方法了
# -*- coding: utf-8 -*-
import unittest
import os
import sys
import six
import traceback
import types
import time
from io import open
from airtest.cli.parser import runner_parser
from airtest.core.api import G, auto_setup, log
from airtest.core.settings import Settings as ST
from airtest.utils.compat import decode_path
from copy import copy
class MyAirtestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.args = args
setup_by_args(args)
# setup script exec scope
cls.scope = copy(globals())
def setUp(self):
if self.args.log and self.args.recording:
for dev in G.DEVICE_LIST:
try:
dev.start_recording()
except:
traceback.print_exc()
# 设置日志路径
auto_setup(logdir=self._logdir)
def tearDown(self):
if self.args.log and self.args.recording:
for k, dev in enumerate(G.DEVICE_LIST):
try:
output = os.path.join(self.args.log, "recording_%d.mp4" % k)
dev.stop_recording(output)
except:
traceback.print_exc()
def runTest(self):
try:
# 调用脚本中的runCase方法并传递scope供脚本使用
exec(self._code["code"], self._code["ns"])
except Exception as err:
tb = traceback.format_exc()
log("Final Error", tb)
six.reraise(*sys.exc_info())
@property
def logdir(self):
return self._logdir
@logdir.setter
def logdir(self, value):
self._logdir = value
@property
def code(self):
return self._code
@code.setter
def code(self, value):
self._code = value
def setup_by_args(args):
# init devices
if isinstance(args.device, list):
devices = args.device
elif args.device:
devices = [args.device]
else:
devices = []
print("do not connect device")
# set base dir to find tpl
args.script = decode_path(args.script)
# set log dir
if args.log is True:
print("save log in %s/log" % args.script)
args.log = os.path.join(args.script, "log")
elif args.log:
print("save log in '%s'" % args.log)
args.log = decode_path(args.log)
else:
print("do not save log")
# guess project_root to be basedir of current .air path
project_root = os.path.dirname(args.script) if not ST.PROJECT_ROOT else None
# 此处不设置日志路径,防止生成多余的log.txt
auto_setup(args.script, devices, None, project_root)
def new_case(py, logdir):
"""实例化MyAirtestCase并绑定runCase方法"""
with open(py, 'r', encoding="utf8") as f:
code = f.read()
obj = compile(code.encode("utf-8"), py, "exec")
ns = {}
ns["__file__"] = py
case = MyAirtestCase()
pyfilename = os.path.basename(py).replace(".py", "")
# 设置属性以便在setUp中设置日志路径
case.logdir = os.path.join(logdir, pyfilename)
case.code = {"code": obj, "ns": ns}
return case
def init_log_folder():
"""初始化日志根目录"""
name = time.strftime("log_%Y%m%d_%H%M%S", time.localtime())
if not os.path.exists(name):
os.mkdir(name)
return name
def run_script(parsed_args, testcase_cls=MyAirtestCase):
global args # make it global deliberately to be used in MyAirtestCase & test scripts
args = parsed_args
dir = os.path.dirname(os.path.realpath(__file__))
suites = []
pys = []
# 获取所有用例集
for f in os.listdir(dir):
if f.endswith("air"):
f = os.path.join(dir, f)
if os.path.isdir(f):
suites.append(f)
# 获取所有脚本
for s in suites:
for f in os.listdir(s):
if f.endswith(".py") and not f.startswith("__"):
pys.append(os.path.join(s, f))
logdir = os.path.join(dir, init_log_folder())
args.log = logdir
suite = unittest.TestSuite()
# 添加脚本
for py in pys:
case = new_case(py, logdir)
suite.addTest(case)
result = unittest.TextTestRunner(verbosity=0).run(suite)
if not result.wasSuccessful():
sys.exit(-1)
if __name__ == "__main__":
ap = runner_parser()
args = ap.parse_args()
run_script(args, MyAirtestCase)
报错是因为需要将测试代码封装成一个 runCase 方法,像下面那样
def runCase(self, vars):
# 业务逻辑代码
pass
代码在这里 new_case 方法
之所以这么整是考虑后面要实现数据驱动,可以通过方法签名里那个 vars 参数来传递数据,这样看起来比直接在命名空间里获取数据稍微清晰一点
代码里加了过滤,文件夹必须以 “用例集” 结尾
代码链接
目录结构如下就能识别出来
case
├─report.py
├─runner.py
├─交易用例集
│ ├──交易失败.py
│ └──交易成功.py
└─登录用例集
├──登录失败.py
└──登录成功.py
参数校验两种方式:
@Valid
注解自动校验public Mono<Map<String, Object>> registerService(@Valid User user) {
return userRepository.save(user).map(u -> {
Map<String, Object> map = new HashMap<>(2);
map.put("status", 1);
return map;
});
}
// User
public class User {
@NotNull
private String username;
@NotNull
private String password;
// getter and setter
}
public Mono<Map<String, Object>> registerService(User user) {
Map<String, Object> map = new HashMap<>(2);
return Mono.just(user).flatMap(u1 -> {
if (u1.getUsername() == null) {
map.put("status", 0);
map.put("reason", "username is null");
return Mono.just(map);
}
if (u1.getPassword() == null) {
map.put("status", 0);
map.put("reason", "password is null");
return Mono.just(map);
}
return userRepository.save(u1).map(u2 -> {
map.put("status", 1);
return map;
});
});
}
如果没记错的话,貌似是这么写
public Mono<Map<String, Object>> registerService(User user) {
return userRepository.save(user).map(u -> {
Map<String, Object> map = new HashMap<>(2);
map.put("status", 1);
return map;
});
}
二路归并改一下,时间复杂度 O(n),空间复杂度 O(1)
public static int middle(int[] a, int[] b) {
int middleIndex = (a.length + b.length + 1) / 2 - 1;
if (middleIndex == 0) {
return -1;
}
int tmp;
int i = 0, j = 0, k = 0;
for (; i < a.length && j < b.length; ++k) {
if (a[i] < b[j]) {
tmp = a[i++];
} else {
tmp = b[j++];
}
if (k == middleIndex) {
return tmp;
}
}
if (i < a.length) {
return a[middleIndex - k + i];
}
return b[middleIndex - k + j];
}
org.testng.IMethodInterceptor
API 中说道:This class is used to alter the list of test methods that TestNG is about to run.
背锅
driver.executeScript("arguments[0].innerText = 'your content'", element);
#1 楼 @michael_wang 那个元素确认已经找到了,下面是 server 的响应,貌似是 server 没有实现对应的方法
unknown command: session/58ebe28bd7ec856ff236702d0abbc84e/element/0.38716481951996684-1/rect
可以将元素定位信息外提形成对象库,Android 和 iOS 分别使用各自的对象库,业务逻辑写一套就行
#14 楼 @debugtalk 谢谢
@debugtalk 楼主我有两个问题:
1、需要判断结果的情况该如何处理,比如一步操作的结果可能会返回 3 种结果:1、2、3,当 1 的时候需要执行操作 A,2 的时候执行操作 B,3 的时候执行操作 C
2、执行时需要前一步的结果,比如输入验证码的过程,需要先获取文本框中的验证码,然后再在编辑框中输入
不知道楼主框架中对这两种情况是如何处理的,本人才疏学浅还望楼主赐教
用 chrome 浏览器 chrome://inspect 可以远程调试 Android 的 webview,html 里面应该是有 id 等属性的吧
你看看这样行不行
xpath:(//LinearLayout)[7]/TextView
另外如果 Android SDK 的路径也带空格的话,运行上面代码也会报错,提示 Internal Server Error
@xdf
@fengliuyishao 直接用 findElementByIosUIAutomation 方法就行,若 element 不可见,则自动滑动到 element 位置。
官方 IOSElement#scrollTo 的 API 有云:
This method is deprecated because it is not consistent and it is going to be removed. It is workaround actually. Recommended to use instead: AppiumDriver.swipe(int, int, int, int, int) MobileElement.swipe(SwipeElementDirection, int) MobileElement.swipe(SwipeElementDirection, int, int, int) or search for elements using MobileBy.ByIosUIAutomation