Merge remote-tracking branch 'iesqa/video-release' into feature/huangbin/ocr_cluster

This commit is contained in:
buyuxiang
2023-11-16 13:10:02 +08:00
6 changed files with 361 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
package uitest
import (
"testing"
"github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
)
func TestAndroidDouyinE2E(t *testing.T) {
testCase := &hrp.TestCase{
Config: hrp.NewConfig("直播_抖音_端到端时延_android").
WithVariables(map[string]interface{}{
"device": "${ENV(SerialNumber)}",
"ups": "${ENV(LIVEUPLIST)}",
}).
SetAndroid(uixt.WithSerialNumber("$device"), uixt.WithAdbLogOn(true)),
TestSteps: []hrp.IStep{
hrp.NewStep("启动抖音").
Android().
AppTerminate("com.ss.android.ugc.aweme").
AppLaunch("com.ss.android.ugc.aweme").
Home().
SwipeToTapApp(
"抖音",
uixt.WithMaxRetryTimes(5),
uixt.WithTapOffset(0, -50),
).
Sleep(20).
Validate().
AssertOCRExists("推荐", "进入抖音失败"),
hrp.NewStep("点击放大镜").
Android().
TapXY(0.9, 0.08).
Sleep(5),
hrp.NewStep("输入账号名称").
Android().
Input("$ups").
Sleep(5),
hrp.NewStep("点击搜索").
Android().
TapByOCR("搜索").
Sleep(5),
hrp.NewStep("端到端采集").Loop(5).
Android().
TapByOCR(
"直播中",
uixt.WithIgnoreNotFoundError(true),
uixt.WithIndex(-1),
).
EndToEndDelay(uixt.WithInterval(5), uixt.WithTimeout(120)).
TapByUITypes(uixt.WithScreenShotUITypes("close")),
},
}
if err := testCase.Dump2JSON("android_e2e_delay_test.json"); err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,146 @@
{
"config": {
"name": "直播_抖音_端到端时延_android",
"variables": {
"device": "${ENV(SerialNumber)}",
"ups": "${ENV(LIVEUPLIST)}"
},
"android": [
{
"serial": "$device",
"log_on": true
}
]
},
"teststeps": [
{
"name": "启动抖音",
"android": {
"actions": [
{
"method": "app_terminate",
"params": "com.ss.android.ugc.aweme"
},
{
"method": "app_launch",
"params": "com.ss.android.ugc.aweme"
},
{
"method": "home"
},
{
"method": "swipe_to_tap_app",
"params": "抖音",
"options": {
"max_retry_times": 5,
"offset": [
0,
-50
]
}
},
{
"method": "sleep",
"params": 20
}
]
},
"validate": [
{
"check": "ui_ocr",
"assert": "exists",
"expect": "推荐",
"msg": "进入抖音失败"
}
]
},
{
"name": "点击放大镜",
"android": {
"actions": [
{
"method": "tap_xy",
"params": [
0.9,
0.08
],
"options": {
}
},
{
"method": "sleep",
"params": 5
}
]
}
},
{
"name": "输入账号名称",
"android": {
"actions": [
{
"method": "input",
"params": "$ups",
"options": {
}
},
{
"method": "sleep",
"params": 5
}
]
}
},
{
"name": "点击搜索",
"android": {
"actions": [
{
"method": "tap_ocr",
"params": "搜索",
"options": {
}
},
{
"method": "sleep",
"params": 5
}
]
}
},
{
"name": "端到端采集",
"android": {
"actions": [
{
"method": "tap_ocr",
"params": "直播中",
"options": {
"ignore_NotFoundError": true,
"index": -1
}
},
{
"method": "live_e2e",
"options": {
"interval": 5,
"timeout": 120
}
},
{
"method": "tap_cv",
"options": {
"screenshot_with_ui_types": [
"close"
]
}
}
]
},
"loops": 5
}
]
}

View File

@@ -59,6 +59,7 @@ const (
ACTION_SwipeToTapTexts ActionMethod = "swipe_to_tap_texts" // swipe up & down to find text and tap
ACTION_VideoCrawler ActionMethod = "video_crawler"
ACTION_ClosePopups ActionMethod = "close_popups"
ACTION_EndToEndDelay ActionMethod = "live_e2e"
)
type MobileAction struct {
@@ -685,6 +686,9 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
return dExt.VideoCrawler(configs)
case ACTION_ClosePopups:
return dExt.ClosePopups(action.GetOptions()...)
case ACTION_EndToEndDelay:
dExt.CollectEndToEndDelay(action.GetOptions()...)
return nil
}
return nil
}

View File

@@ -128,12 +128,14 @@ type cacheStepData struct {
screenResults ScreenResultMap
// cache feed/live video stat
videoCrawler *VideoCrawler
e2eDelay []timeLog
}
func (d *cacheStepData) reset() {
d.screenShots = make([]string, 0)
d.screenResults = make(map[string]*ScreenResult)
d.videoCrawler = nil
d.e2eDelay = nil
}
type DriverExt struct {
@@ -281,6 +283,7 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
cacheData["screenshots_urls"] = dExt.cacheStepData.screenResults.getScreenShotUrls()
dExt.cacheStepData.screenResults.updatePopupCloseStatus()
cacheData["screen_results"] = dExt.cacheStepData.screenResults
cacheData["e2e_results"] = dExt.cacheStepData.e2eDelay
// clear cache
dExt.cacheStepData.reset()

140
hrp/pkg/uixt/live_e2e.go Normal file
View File

@@ -0,0 +1,140 @@
package uixt
import (
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/rs/zerolog/log"
)
type timeLog struct {
UTCTimeStr string `json:"utc_time_str"`
UTCTime int64 `json:"utc_time"`
LiveTimeStr string `json:"live_time_str"`
LiveTime int64 `json:"live_time"`
Delay float64 `json:"delay"`
}
type EndToEndDelay struct {
driver *DriverExt
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
Interval int `json:"interval"` // seconds
Duration int `json:"duration"` // seconds
Timelines []timeLog `json:"timelines"`
}
func (dExt *DriverExt) CollectEndToEndDelay(options ...ActionOption) {
dataOptions := NewActionOptions(options...)
startTime := time.Now()
if dataOptions.Interval == 0 {
dataOptions.Interval = 5
}
if dataOptions.Timeout == 0 {
dataOptions.Timeout = 60
}
endToEndDelay := &EndToEndDelay{
driver: dExt,
Duration: int(dataOptions.Timeout),
Interval: int(dataOptions.Interval),
StartTime: startTime.Format("2006-01-02 15:04:05"),
}
endToEndDelay.Start()
dExt.cacheStepData.e2eDelay = endToEndDelay.Timelines
}
func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
utcTimeStr := utcTime.Format("2006-01-02 15:04:05")
ocrTexts, err := ete.driver.GetScreenTexts()
if err != nil {
log.Error().Err(err).Msg("get ocr texts failed")
return err
}
// filter ocr texts with time format
var liveTimeTexts []string
for _, ocrText := range ocrTexts {
if len(ocrText.Text) < 10 || strings.Contains(ocrText.Text, ":") {
continue
}
// exclude digit(s) recognized as letter(s)
_, errParseInt := strconv.ParseInt(ocrText.Text[:10], 10, 64)
if errParseInt != nil {
continue
}
liveTimeTexts = append(liveTimeTexts, ocrText.Text)
}
var liveTimeText string
if len(liveTimeTexts) != 0 {
liveTimeText = liveTimeTexts[0]
} else {
log.Warn().Msg("no time text found")
return nil
}
if len(liveTimeText) < 13 {
for (13 - len(liveTimeText)) > 0 {
liveTimeText += "0"
}
}
liveTimeInt, err := strconv.Atoi(liveTimeText)
if err != nil {
liveTimeInt = 0
}
liveTimeSInt, err := strconv.Atoi(liveTimeText[:10])
if err != nil {
liveTimeSInt = 0
}
liveTimeNSInt, err := strconv.Atoi(liveTimeText[10:13])
if err != nil {
liveTimeNSInt = 0
}
liveTimeStr := time.Unix(int64(liveTimeSInt), int64(liveTimeNSInt*1000*1000)).Format("2006-01-02 15:04:05")
log.Info().
Str("utcTime", utcTimeStr).
Int64("utcTimeInt", utcTime.UnixMilli()).
Str("liveTime", liveTimeStr).
Int64("liveTimeInt", int64(liveTimeInt)).
Float64("delay", float64(utcTime.UnixMilli()-int64(liveTimeInt))/1000).
Msg("log live time")
ete.Timelines = append(ete.Timelines, timeLog{
UTCTimeStr: utcTimeStr,
UTCTime: utcTime.UnixMilli(),
LiveTimeStr: liveTimeStr,
LiveTime: int64(liveTimeInt),
Delay: float64(utcTime.UnixMilli()-int64(liveTimeInt)) / 1000,
})
return nil
}
func (ete *EndToEndDelay) Start() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
timer := time.NewTimer(time.Duration(ete.Duration) * time.Second)
for {
select {
case <-timer.C:
ete.EndTime = time.Now().Format("2006-01-02 15:04:05")
return
case <-c:
ete.EndTime = time.Now().Format("2006-01-02 15:04:05")
return
default:
utcTime := time.Now()
if utcTime.Unix()%int64(ete.Interval) == 0 {
_ = ete.getCurrentLiveTime(utcTime)
} else {
time.Sleep(500 * time.Millisecond)
}
}
}
}

View File

@@ -300,6 +300,15 @@ func (s *StepMobile) VideoCrawler(params map[string]interface{}) *StepMobile {
return &StepMobile{step: s.step}
}
func (s *StepMobile) EndToEndDelay(options ...uixt.ActionOption) *StepMobile {
s.mobileStep().Actions = append(s.mobileStep().Actions, uixt.MobileAction{
Method: uixt.ACTION_EndToEndDelay,
Params: nil,
Options: uixt.NewActionOptions(options...),
})
return &StepMobile{step: s.step}
}
func (s *StepMobile) ScreenShot(options ...uixt.ActionOption) *StepMobile {
s.mobileStep().Actions = append(s.mobileStep().Actions, uixt.MobileAction{
Method: uixt.ACTION_ScreenShot,