Merge branch 'feature/huangbin/stability_optimization' into 'video-release'

Feature/huangbin/stability optimization

See merge request iesqa/httprunner!28
This commit is contained in:
徐聪
2024-03-19 09:46:16 +00:00
13 changed files with 399 additions and 134 deletions

View File

@@ -1,5 +1,15 @@
# Release History
## v4.3.8 (2023-09-19)
- feat: OCR calls use the high-precision cluster interface, and the default timeout is changed from 3s to 10s
- feat: use jpeg to compress screenshots
- feat: increase live broadcast end-to-end collection capabilities
- fix: add file reading and writing methods as a log management logic
- fix: throws an error when the number of use cases is 0
- fix: fixed the problem that the new version of adb format fails to parse
**go version**
## v4.3.7 (2023-09-19)
**go version**

View File

@@ -0,0 +1,66 @@
{
"config": {
"name": "直播_抖音_关注天窗_android",
"variables": {
"device": "${ENV(SerialNumber)}",
"ups": "${ENV(LIVEUPLIST)}"
},
"android": [
{
"serial": "$device",
"log_on": true,
"close_popup": true
}
]
},
"teststeps": [
{
"name": "启动抖音",
"android": {
"actions": [
{
"method": "app_terminate",
"params": "com.ss.android.ugc.aweme"
},
{
"method": "app_launch",
"params": "com.ss.android.ugc.aweme"
}
]
}
},
{
"name": "处理青少年弹窗",
"android": {
"actions": [
{
"method": "tap_ocr",
"params": "我知道了",
"ignore_NotFoundError": true
}
]
},
"validate": [
{
"check": "ui_ocr",
"assert": "exists",
"expect": "推荐",
"msg": "进入抖音失败"
}
]
},
{
"name": "点击关注",
"android": {
"actions": [
{
"method": "tap_ocr",
"params": "关注",
"index": -1,
"identifier": "click_live_new"
}
]
}
}
]
}

View File

@@ -1,27 +1,223 @@
{
"config": {
"name": "播_抖音_滑动场景_随机间隔_android",
"name": "播_抖极_feed卡片_android",
"variables": {
"device": "${ENV(SerialNumber)}"
},
"android": [
{
"serial": "$device"
"serial": "$device",
"log_on": true,
"close_popup": true
}
]
},
"teststeps": [
{
"name": "启动抖音",
"name": "清理android无关进程",
"android": {
"actions": [
{
"method": "app_terminate",
"params": "com.ss.android.ugc.aweme"
},
{
"method": "app_terminate",
"params": "com.ss.android.ugc.aweme.lite"
},
{
"method": "app_terminate",
"params": "com.smile.gifmaker"
},
{
"method": "app_terminate",
"params": "com.kuaishou.nebula"
},
{
"method": "app_terminate",
"params": "com.tencent.mm"
},
{
"method": "app_terminate",
"params": "com.duowan.kiwi"
},
{
"method": "app_terminate",
"params": "air.tv.douyu.android"
},
{
"method": "app_terminate",
"params": "com.xingin.xhs"
},
{
"method": "app_terminate",
"params": "com.taobao.taobao"
},
{
"method": "app_terminate",
"params": "tv.danmaku.bili"
},
{
"method": "app_terminate",
"params": "com.cmcc.cmvideo"
},
{
"method": "app_terminate",
"params": "com.xunmeng.pinduoduo"
},
{
"method": "app_terminate",
"params": "com.cctv.yangshipin.app.androidp"
}
]
}
},
{
"name": "启动抖音极速版",
"android": {
"actions": [
{
"method": "app_terminate",
"params": "com.ss.android.ugc.aweme.lite"
},
{
"method": "app_launch",
"params": "com.ss.android.ugc.aweme"
"params": "com.ss.android.ugc.aweme.lite"
},
{
"method": "sleep",
"params": 10
},
{
"method": "close_popups",
"options": {
"max_retry_times": 2
}
}
]
},
"validate": [
{
"check": "ui_foreground_app",
"assert": "equal",
"expect": "com.ss.android.ugc.aweme.lite",
"msg": "app [com.ss.android.ugc.aweme.lite] should be in foreground"
}
]
},
{
"name": "处理通讯录弹窗",
"android": {
"actions": [
{
"method": "tap_ocr",
"params": "拒绝",
"ignore_NotFoundError": true
}
]
}
},
{
"name": "处理青少年弹窗",
"android": {
"actions": [
{
"method": "sleep",
"params": 5
},
{
"method": "tap_ocr",
"params": "我知道了",
"ignore_NotFoundError": true
}
]
}
},
{
"name": "点击直播标签,进入直播间",
"android": {
"actions": [
{
"method": "swipe_to_tap_texts",
"params": [
"看直播开宝箱",
"最高领",
"点击进入直播间"
],
"identifier": "click_live_new",
"max_retry_times": 40,
"wait_time": 2,
"direction": [
0.5,
0.8,
0.5,
0.2
],
"scope": [
0.1,
0.5,
0.9,
0.9
],
"offset": [
0,
-100
]
}
]
}
},
{
"name": "等待30s",
"android": {
"actions": [
{
"method": "sleep",
"params": 30
}
]
}
},
{
"name": "下滑进入下一个直播间",
"android": {
"actions": [
{
"method": "swipe",
"params": [
0.5,
0.7,
0.5,
0.1
],
"identifier": "slide_in_live_new"
},
{
"method": "sleep",
"params": 30
}
]
}
},
{
"name": "返回主界面,并打开本地时间戳",
"android": {
"actions": [
{
"method": "app_terminate",
"params": "com.ss.android.ugc.aweme.lite"
},
{
"method": "home"
},
{
"method": "swipe_to_tap_app",
"params": "local",
"max_retry_times": 5,
"offset": [
0,
-50
]
},
{
"method": "sleep",
@@ -31,107 +227,10 @@
},
"validate": [
{
"check": "ui_foreground_app",
"assert": "equal",
"expect": "com.ss.android.ugc.aweme",
"msg": "app [com.ss.android.ugc.aweme] should be in foreground"
}
]
},
{
"name": "处理青少年弹窗",
"android": {
"actions": [
{
"method": "tap_ocr",
"params": "我知道了",
"options": {
"ignore_NotFoundError": true
}
}
]
}
},
{
"name": "滑动 Feed 3 次,随机间隔 0-5s",
"android": {
"actions": [
{
"method": "swipe",
"params": "up",
"options": {}
},
{
"method": "sleep_random",
"params": [
0,
5
]
}
]
},
"loops": 3
},
{
"name": "滑动 Feed 1 次,随机间隔 5-10s",
"android": {
"actions": [
{
"method": "swipe",
"params": "up",
"options": {}
},
{
"method": "sleep_random",
"params": [
5,
10
]
}
]
},
"loops": 1
},
{
"name": "滑动 Feed 10 次70% 随机间隔 0-5s30% 随机间隔 5-10s",
"android": {
"actions": [
{
"method": "swipe",
"params": "up",
"options": {}
},
{
"method": "sleep_random",
"params": [
0,
5,
0.7,
5,
10,
0.3
]
}
]
},
"loops": 10
},
{
"name": "exit",
"android": {
"actions": [
{
"method": "app_terminate",
"params": "com.ss.android.ugc.aweme"
}
]
},
"validate": [
{
"check": "ui_foreground_app",
"assert": "not_equal",
"expect": "com.ss.android.ugc.aweme",
"msg": "app [com.ss.android.ugc.aweme] should not be in foreground"
"check": "ui_ocr",
"assert": "exists",
"expect": "17",
"msg": "打开本地时间戳失败"
}
]
}

View File

@@ -41,7 +41,11 @@ var listAndroidDevicesCmd = &cobra.Command{
if isDetail {
fmt.Println(format(d.DeviceInfo()))
} else {
fmt.Println(d.Serial(), d.Usb())
if usb, err := d.Usb(); err != nil {
fmt.Println(d.Serial())
} else {
fmt.Println(d.Serial(), usb)
}
}
}
return nil

View File

@@ -22,15 +22,18 @@ var (
const (
ResultsDirName = "results"
ScreenshotsDirName = "screenshots"
ActionLogDireName = "action_log"
)
var (
RootDir string
ResultsDir string
ResultsPath string
ScreenShotsPath string
StartTime = time.Now()
StartTimeStr = StartTime.Format("20060102150405")
RootDir string
ResultsDir string
ResultsPath string
ScreenShotsPath string
StartTime = time.Now()
StartTimeStr = StartTime.Format("20060102150405")
ActionLogFilePath string
DeviceActionLogFilePath string
)
func init() {
@@ -43,4 +46,6 @@ func init() {
ResultsDir = filepath.Join(ResultsDirName, StartTimeStr)
ResultsPath = filepath.Join(RootDir, ResultsDir)
ScreenShotsPath = filepath.Join(ResultsPath, ScreenshotsDirName)
ActionLogFilePath = filepath.Join(ResultsDir, ActionLogDireName)
DeviceActionLogFilePath = "/sdcard/Android/data/io.appium.uiautomator2.server/files/hodor"
}

View File

@@ -1 +1 @@
v4.3.6.2311301910
v4.3.8

View File

@@ -53,6 +53,7 @@ func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) {
testCasePath := TestCasePath(path)
tc, err := testCasePath.ToTestCase()
if err != nil {
log.Error().Err(err).Msg("fail to parse test:")
return nil
}
testCases = append(testCases, tc)
@@ -63,6 +64,10 @@ func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) {
}
}
if len(testCases) < 1 {
return nil, errors.New("test case count less than 1 or parse error")
}
log.Info().Int("count", len(testCases)).Msg("load testcases successfully")
return testCases, nil
}

View File

@@ -108,7 +108,7 @@ func (c Client) DeviceList() (devices []*Device, err error) {
}
fields := strings.Fields(line)
if len(fields) < 5 || len(fields[0]) == 0 {
if len(fields) < 4 || len(fields[0]) == 0 {
log.Error().Str("line", line).Msg("get unexpected line")
continue
}
@@ -117,6 +117,9 @@ func (c Client) DeviceList() (devices []*Device, err error) {
mapAttrs := map[string]string{}
for _, field := range sliceAttrs {
split := strings.Split(field, ":")
if len(split) == 1 {
continue
}
key, val := split[0], split[1]
mapAttrs[key] = val
}

View File

@@ -107,20 +107,37 @@ func (d *Device) features() (features Features, err error) {
return features, nil
}
func (d *Device) Product() string {
return d.attrs["product"]
func (d Device) HasAttribute(key string) bool {
_, ok := d.attrs[key]
return ok
}
func (d *Device) Model() string {
return d.attrs["model"]
func (d Device) Product() (string, error) {
if d.HasAttribute("product") {
return d.attrs["product"], nil
}
return "", errors.New("does not have attribute: product")
}
func (d *Device) Usb() string {
return d.attrs["usb"]
func (d Device) Model() (string, error) {
if d.HasAttribute("model") {
return d.attrs["model"], nil
}
return "", errors.New("does not have attribute: model")
}
func (d *Device) transportId() string {
return d.attrs["transport_id"]
func (d *Device) Usb() (string, error) {
if d.HasAttribute("usb") {
return d.attrs["usb"], nil
}
return "", errors.New("does not have attribute: usb")
}
func (d Device) transportId() (string, error) {
if d.HasAttribute("transport_id") {
return d.attrs["transport_id"], nil
}
return "", errors.New("does not have attribute: transport_id")
}
func (d *Device) DeviceInfo() map[string]string {
@@ -132,8 +149,13 @@ func (d *Device) Serial() string {
return d.serial
}
func (d *Device) IsUsb() bool {
return d.Usb() != ""
func (d *Device) IsUsb() (bool, error) {
usb, err := d.Usb()
if err != nil {
return false, err
}
return usb != "", nil
}
func (d *Device) State() (DeviceState, error) {

View File

@@ -3,6 +3,9 @@ package uixt
import (
"bytes"
"fmt"
"io/fs"
"io/ioutil"
"path/filepath"
"strconv"
"strings"
"time"
@@ -10,7 +13,9 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/funplugin/myexec"
"github.com/httprunner/httprunner/v4/hrp/internal/code"
"github.com/httprunner/httprunner/v4/hrp/internal/env"
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
)
@@ -457,7 +462,41 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
}
content := ad.logcat.logBuffer.String()
log.Info().Str("logcat content", content).Msg("display logcat content")
return ConvertPoints(content), nil
pointRes := ConvertPoints(content)
// 没有解析到打点日志,走兜底逻辑
if len(pointRes) == 0 {
log.Info().Msg("action log is null, use action file >>>")
logFilePathPrefix := fmt.Sprintf("%v/data", env.ActionLogFilePath)
files := []string{}
myexec.RunCommand("adb", "-s", ad.adbClient.Serial(), "pull", env.DeviceActionLogFilePath, env.ActionLogFilePath)
err = filepath.Walk(env.ActionLogFilePath, func(path string, info fs.FileInfo, err error) error {
// 只是需要日志文件
if ok := strings.Contains(path, logFilePathPrefix); ok {
files = append(files, path)
}
return nil
})
// 先保持原有状态码不变这里不return error
if err != nil {
log.Error().Err(err).Msg("read log file fail")
return pointRes, nil
}
if len(files) != 1 {
log.Error().Err(err).Msg("log file count error")
return pointRes, nil
}
data, err := ioutil.ReadFile(files[0])
if err != nil {
log.Info().Msg("read File error")
return pointRes, nil
}
pointRes = ConvertPoints(string(data))
}
return pointRes, nil
}
func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) {

View File

@@ -4,9 +4,9 @@ import (
"bytes"
"fmt"
"image"
"image/gif"
_ "image/gif"
"image/jpeg"
"image/png"
_ "image/png"
"math/rand"
"mime"
"mime/multipart"
@@ -241,7 +241,8 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin
return "", errors.Wrap(err, "decode screenshot image failed")
}
screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, format))
// The default format uses jpeg for compression
screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, "jpeg"))
file, err := os.Create(screenshotPath)
if err != nil {
return "", errors.Wrap(err, "create screenshot image file failed")
@@ -251,12 +252,10 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin
}()
switch format {
case "png":
err = png.Encode(file, img)
case "jpeg":
err = jpeg.Encode(file, img, nil)
case "gif":
err = gif.Encode(file, img, nil)
// Convert to jpeg uniformly and compress with a compression rate of 95
case "jpeg", "png":
jpegOptions := &jpeg.Options{Quality: 95}
err = jpeg.Encode(file, img, jpegOptions)
default:
return "", fmt.Errorf("unsupported image format: %s", format)
}

View File

@@ -217,12 +217,18 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp
for _, uiType := range actionOptions.ScreenShotWithUITypes {
bodyWriter.WriteField("uiTypes", uiType)
}
// 使用高精度集群
bodyWriter.WriteField("ocrCluster", "highPrecision")
if actionOptions.ScreenShotWithOCRCluster != "" {
bodyWriter.WriteField("ocrCluster", actionOptions.ScreenShotWithOCRCluster)
}
if actionOptions.Timeout > 0 {
bodyWriter.WriteField("timeout", fmt.Sprintf("%v", actionOptions.Timeout))
} else {
bodyWriter.WriteField("timeout", fmt.Sprintf("%v", 10))
}
formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png")

View File

@@ -18,6 +18,7 @@ import (
"github.com/gorilla/websocket"
"github.com/httprunner/funplugin"
"github.com/httprunner/funplugin/myexec"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
@@ -25,6 +26,7 @@ import (
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/code"
"github.com/httprunner/httprunner/v4/hrp/internal/env"
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
"github.com/httprunner/httprunner/v4/hrp/internal/version"
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
@@ -523,6 +525,11 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
config := r.caseRunner.testCase.Config
log.Info().Str("testcase", config.Name).Msg("run testcase start")
// 安卓系统删除打点日志文件
if r.caseRunner.testCase.Config.Android != nil {
myexec.RunCommand("adb", "-s", r.caseRunner.testCase.Config.Android[0].SerialNumber, "shell", "rm", "-r", env.DeviceActionLogFilePath)
}
// update config variables with given variables
r.InitWithParameters(givenVars)