refactor: remove video crawler

This commit is contained in:
lilong.129
2024-08-27 20:38:46 +08:00
parent 11cfae5e5d
commit 01c76c6ed8
11 changed files with 3 additions and 948 deletions

View File

@@ -335,92 +335,6 @@
}
]
},
{
"name": "【点播】滑动消费",
"android": {
"actions": [
{
"method": "video_crawler",
"params": {
"feed": {
"sleep_random": [
0,
5,
0.6,
5,
15,
0.2,
15,
50,
0.2
],
"target_count": 10,
"target_labels": [
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^广告$"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^图文$"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^特效\\|"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^模板\\|"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^购物\\|"
}
]
},
"live": {
"sleep_random": [
20,
20
],
"target_count": 0
},
"timeout": 600
}
}
]
}
},
{
"name": "返回主界面,并打开本地时间戳",
"android": {

View File

@@ -1,142 +0,0 @@
{
"config": {
"name": "抓取抖音视频信息",
"variables": {
"device": "${ENV(SerialNumber)}"
},
"android": [
{
"serial": "$device"
}
]
},
"teststeps": [
{
"name": "启动 app",
"android": {
"actions": [
{
"method": "app_launch",
"params": "com.ss.android.ugc.aweme"
},
{
"method": "sleep",
"params": 5
}
]
},
"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": "滑动消费 feed 至少 10 个live 至少 3 个滑动过程中70% 随机间隔 0-5s30% 随机间隔 5-10s",
"android": {
"actions": [
{
"method": "video_crawler",
"params": {
"feed": {
"sleep_random": [
0,
5,
0.7,
5,
10,
0.3
],
"target_count": 5,
"target_labels": [
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"target": 1,
"text": "^广告$"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"target": 1,
"text": "^图文$"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^特效\\|"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^模板\\|"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^购物\\|"
}
]
},
"live": {
"sleep_random": [
15,
20
],
"target_count": 3
},
"timeout": 600
}
}
]
}
},
{
"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"
}
]
}
]
}

View File

@@ -1,63 +0,0 @@
//go:build localtest
package uitest
import (
"testing"
"github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
)
func TestAndroidVideoCrawlerTest(t *testing.T) {
testCase := &hrp.TestCase{
Config: hrp.NewConfig("抓取抖音视频信息").
WithVariables(map[string]interface{}{
"device": "${ENV(SerialNumber)}",
}).
SetAndroid(uixt.WithSerialNumber("$device")),
TestSteps: []hrp.IStep{
hrp.NewStep("启动 app").
Android().
ScreenShot(uixt.WithScreenShotOCR(true), uixt.WithScreenShotUpload(true)).
AppLaunch("com.ss.android.ugc.aweme").
Sleep(5).
Validate().
AssertAppInForeground("com.ss.android.ugc.aweme"),
hrp.NewStep("滑动消费 feed 至少 10 个live 至少 3 个滑动过程中70% 随机间隔 0-5s30% 随机间隔 5-10s").
Android().
VideoCrawler(map[string]interface{}{
"timeout": 600,
"feed": map[string]interface{}{
"target_count": 5,
"target_labels": []map[string]interface{}{
{"text": "^广告$", "scope": []float64{0, 0.5, 1, 1}, "regex": true, "target": 1},
{"text": "^图文$", "scope": []float64{0, 0.5, 1, 1}, "regex": true, "target": 1},
{"text": `^特效\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": `^模板\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": `^购物\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
},
"sleep_random": []float64{0, 5, 0.7, 5, 10, 0.3},
},
"live": map[string]interface{}{
"target_count": 3,
"sleep_random": []float64{15, 20},
},
}),
hrp.NewStep("exit").
Android().
AppTerminate("com.ss.android.ugc.aweme").
Validate().
AssertAppNotInForeground("com.ss.android.ugc.aweme"),
},
}
if err := testCase.Dump2JSON("demo_android_video_crawler.json"); err != nil {
t.Fatal(err)
}
err := hrp.Run(t, testCase)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -121,27 +121,6 @@ func TestAndroidExpertTest(t *testing.T) {
).
Validate().
AssertOCRExists("推荐", "进入抖音失败"),
// 点播赛道
hrp.NewStep("【点播】滑动消费").
Android().
VideoCrawler(map[string]interface{}{
"timeout": 600,
"feed": map[string]interface{}{
"target_count": 10,
"target_labels": []map[string]interface{}{
{"text": "^广告$", "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": "^图文$", "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": `^特效\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": `^模板\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": `^购物\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
},
"sleep_random": []float64{0, 5, 0.6, 5, 15, 0.2, 15, 50, 0.2},
},
"live": map[string]interface{}{
"target_count": 0,
"sleep_random": []float64{20, 20},
},
}),
// localtime 时间戳界面
hrp.NewStep("返回主界面,并打开本地时间戳").
Android().
@@ -275,27 +254,6 @@ func TestIOSExpertTest(t *testing.T) {
).
Validate().
AssertOCRExists("推荐", "进入抖音失败"),
// 点播赛道
hrp.NewStep("【点播】滑动消费").
IOS().
VideoCrawler(map[string]interface{}{
"timeout": 600,
"feed": map[string]interface{}{
"target_count": 10,
"target_labels": []map[string]interface{}{
{"text": "^广告$", "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": "^图文$", "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": `^特效\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": `^模板\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
{"text": `^购物\|`, "scope": []float64{0, 0.5, 1, 1}, "regex": true},
},
"sleep_random": []float64{0, 5, 0.6, 5, 15, 0.2, 15, 50, 0.2},
},
"live": map[string]interface{}{
"target_count": 0,
"sleep_random": []float64{20, 20},
},
}),
// localtime 时间戳界面
hrp.NewStep("返回主界面,并打开本地时间戳").
IOS().

View File

@@ -320,92 +320,6 @@
}
]
},
{
"name": "【点播】滑动消费",
"ios": {
"actions": [
{
"method": "video_crawler",
"params": {
"feed": {
"sleep_random": [
0,
5,
0.6,
5,
15,
0.2,
15,
50,
0.2
],
"target_count": 10,
"target_labels": [
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^广告$"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^图文$"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^特效\\|"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^模板\\|"
},
{
"regex": true,
"scope": [
0,
0.5,
1,
1
],
"text": "^购物\\|"
}
]
},
"live": {
"sleep_random": [
20,
20
],
"target_count": 0
},
"timeout": 600
}
}
]
}
},
{
"name": "返回主界面,并打开本地时间戳",
"ios": {

View File

@@ -62,7 +62,6 @@ const (
ACTION_SwipeToTapApp ActionMethod = "swipe_to_tap_app" // swipe left & right to find app and tap
ACTION_SwipeToTapText ActionMethod = "swipe_to_tap_text" // swipe up & down to find text and tap
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"
ACTION_InstallApp ActionMethod = "install_app"
@@ -740,17 +739,6 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
return dExt.Driver.StartCamera()
case ACTION_StopCamera:
return dExt.Driver.StopCamera()
case ACTION_VideoCrawler:
params, ok := action.Params.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid video crawler params: %v(%T)", action.Params, action.Params)
}
data, _ := json.Marshal(params)
configs := &VideoCrawlerConfigs{}
if err := json.Unmarshal(data, configs); err != nil {
return errors.Wrapf(err, "invalid video crawler params: %v(%T)", action.Params, action.Params)
}
return dExt.VideoCrawler(configs)
case ACTION_ClosePopups:
return dExt.ClosePopupsHandler()
case ACTION_EndToEndDelay:

View File

@@ -59,7 +59,6 @@ type ScreenResult struct {
Texts OCRTexts `json:"texts"` // dumped raw OCRTexts
Icons UIResultMap `json:"icons"` // CV 识别的图标
Tags []string `json:"tags"` // tags for image, e.g. ["feed", "ad", "live"]
Video *Video `json:"video,omitempty"`
Popup *PopupInfo `json:"popup,omitempty"`
SwipeStartTime int64 `json:"swipe_start_time"` // 滑动开始时间戳
@@ -94,15 +93,13 @@ type cacheStepData struct {
screenShots []string
// cache step screenshot ocr results, key is image path, value is ScreenResult
screenResults ScreenResultMap
// cache feed/live video stat
videoCrawler *VideoCrawler
e2eDelay []timeLog
// cache e2e delay
e2eDelay []timeLog
}
func (d *cacheStepData) reset() {
d.screenShots = make([]string, 0)
d.screenResults = make(map[string]*ScreenResult)
d.videoCrawler = nil
d.e2eDelay = nil
}
@@ -331,7 +328,6 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin
func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
cacheData := make(map[string]interface{})
cacheData["video_stat"] = dExt.cacheStepData.videoCrawler
cacheData["screenshots"] = dExt.cacheStepData.screenShots
cacheData["screenshots_urls"] = dExt.cacheStepData.screenResults.getScreenShotUrls()

View File

@@ -418,7 +418,6 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S
screenResult.Texts = imageResult.OCRResult.ToOCRTexts()
screenResult.UploadedURL = imageResult.URL
screenResult.Icons = imageResult.UIResult
screenResult.Video = &Video{LiveType: imageResult.LiveType, ViewCount: imageResult.LivePopularity}
if actionOptions.ScreenShotWithClosePopups && imageResult.ClosePopupsResult != nil {
screenResult.Popup = &PopupInfo{

View File

@@ -1,467 +0,0 @@
package uixt
import (
"math"
"math/rand"
"time"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/code"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
)
type TargetLabel struct {
Text string `json:"text"`
Scope Scope `json:"scope"`
Regex bool `json:"regex"`
Target int `json:"target"` // target count for current label
}
type FeedConfig struct {
TargetCount int `json:"target_count"`
TargetLabels []TargetLabel `json:"target_labels"`
SleepRandom []interface{} `json:"sleep_random"`
}
type LiveConfig struct {
TargetCount int `json:"target_count"`
TargetLabels []TargetLabel `json:"target_labels"`
SleepRandom []interface{} `json:"sleep_random"`
}
type VideoCrawlerConfigs struct {
Timeout int `json:"timeout"` // seconds
Feed FeedConfig `json:"feed"`
Live LiveConfig `json:"live"`
}
type VideoCrawler struct {
driverExt *DriverExt
configs *VideoCrawlerConfigs
timer *time.Timer
// used to help checking if swipe success
failedCount int64
FeedCount int `json:"feed_count"`
FeedStat map[string]int `json:"feed_stat"` // 分类统计 feed 数量:视频/图文/广告/特效/模板/购物
LiveCount int `json:"live_count"`
LiveStat map[string]int `json:"live_stat"` // 分类统计 live 数量:秀场/游戏/电商/多人
}
func (vc *VideoCrawler) isFeedTargetAchieved() bool {
targetStat := make(map[string]int)
for _, targetLabel := range vc.configs.Feed.TargetLabels {
targetStat[targetLabel.Text] = targetLabel.Target
}
log.Info().
Int("current_total", vc.FeedCount).
Interface("current_stat", vc.FeedStat).
Int("target_total", vc.configs.Feed.TargetCount).
Interface("target_stat", targetStat).
Msg("display feed crawler progress")
// check total feed count
if vc.FeedCount < vc.configs.Feed.TargetCount {
return false
}
// check each feed type's count
for _, targetLabel := range vc.configs.Feed.TargetLabels {
if vc.FeedStat[targetLabel.Text] < targetLabel.Target {
return false
}
}
return true
}
func (vc *VideoCrawler) isLiveTargetAchieved() bool {
targetStat := make(map[string]int)
for _, targetLabel := range vc.configs.Live.TargetLabels {
targetStat[targetLabel.Text] = targetLabel.Target
}
log.Info().
Int("current_total", vc.LiveCount).
Interface("current_stat", vc.LiveStat).
Int("target_total", vc.configs.Live.TargetCount).
Interface("target_stat", targetStat).
Msg("display live crawler progress")
// check total live count
if vc.LiveCount < vc.configs.Live.TargetCount {
return false
}
// check each live type's count
for _, targetLabel := range vc.configs.Live.TargetLabels {
if vc.LiveStat[targetLabel.Text] < targetLabel.Target {
return false
}
}
return true
}
func (vc *VideoCrawler) isTargetAchieved() bool {
return vc.isFeedTargetAchieved() && vc.isLiveTargetAchieved()
}
func (vc *VideoCrawler) exitLiveRoom() error {
log.Info().Msg("press back to exit live room")
err := vc.driverExt.Driver.PressBack()
time.Sleep(time.Duration(3) * time.Second)
if vc.driverExt.TapByOCR("退出直播间") == nil {
log.Info().Msg("clicked the button to exit the live room successfully")
}
return err
}
const (
FOUND_FEED_SUCCESS = "found feed success"
FOUND_LIVE_SUCCESS = "found live success"
)
func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) {
if dExt.plugin == nil {
return errors.New("miss plugin for video crawler")
}
// set default sleep random strategy if not set
if configs.Feed.SleepRandom == nil {
configs.Feed.SleepRandom = []interface{}{1, 5}
}
if configs.Live.SleepRandom == nil {
configs.Live.SleepRandom = []interface{}{10, 15}
}
crawler := &VideoCrawler{
driverExt: dExt,
configs: configs,
failedCount: 0,
FeedCount: 0,
FeedStat: make(map[string]int),
LiveCount: 0,
LiveStat: make(map[string]int),
}
defer func() {
dExt.cacheStepData.videoCrawler = crawler
}()
// flag仅当 flag 为 false 时,并处于内流时,才执行退出直播间逻辑
isFeed := true
// loop until target count achieved or timeout
// the main loop is feed crawler
crawler.timer = time.NewTimer(time.Duration(configs.Timeout) * time.Second)
for {
select {
case <-crawler.timer.C:
log.Warn().Msg("timeout in feed crawler")
return errors.Wrap(code.TimeoutError, "feed crawler timeout")
case <-dExt.interruptSignal:
log.Warn().Msg("interrupted in feed crawler")
return errors.Wrap(code.InterruptError, "feed crawler interrupted")
default:
if err = crawler.clearCurrentVideo(); err != nil {
log.Error().Err(err).Msg("clear cache failed")
}
// swipe to next feed video
log.Info().Msg("swipe to next feed video")
swipeStartTime := time.Now()
if err = dExt.SwipeUpUtil(crawler.failedCount, WithOffsetRandomRange(-10, 10)); err != nil {
log.Error().Err(err).Msg("feed swipe up failed")
return err
}
swipeFinishTime := time.Now()
// get app event trackings
// retry 3 times if get feed failed, abort if fail 3 consecutive times
fetchVideoStartTime := time.Now()
currentVideo, err := crawler.getCurrentVideo()
if err != nil || currentVideo.Type == "" {
crawler.failedCount++
if crawler.failedCount >= 3 {
// failed 3 consecutive times
return errors.Wrap(code.TrackingGetError,
"get current feed video failed 3 consecutive times")
}
log.Warn().
Int64("failedCount", crawler.failedCount).
Msg("get current feed video failed")
// check and handle popups
if err := crawler.driverExt.ClosePopupsHandler(); err != nil {
return err
}
// retry
continue
}
fetchVideoFinishTime := time.Now()
// 直播预览流线上概率
livePreviewProb := crawler.getLivePreviewProb()
switch currentVideo.Type {
case VideoType_PreviewLive:
isFeed = true
// 直播预览流
var skipEnterLive bool
if crawler.isLiveTargetAchieved() {
log.Info().Interface("video", currentVideo).
Msg("live count achieved, skip entering live room")
skipEnterLive = true
} else if rand.Float64() <= livePreviewProb {
log.Info().Interface("livePreviewProb", livePreviewProb).Msg("skip entering preview")
skipEnterLive = true
}
if !skipEnterLive {
time.Sleep(1 * time.Second)
// enter live room
entryPoint := PointF{
X: float64(dExt.WindowSize.Width / 2),
Y: float64(dExt.WindowSize.Height / 2),
}
log.Info().Msg("tap screen center to enter live room")
if err := crawler.driverExt.TapAbsXY(entryPoint.X, entryPoint.Y,
WithOffsetRandomRange(-20, 20)); err != nil {
log.Error().Err(err).Msg("tap live video failed")
continue
}
currentVideo.Type = VideoType_Live
} else {
// skip entering live room
// only mock simulation play duration
sleepTime := math.Min(float64(currentVideo.SimulationPlayDuration), float64(currentVideo.RandomPlayDuration))
currentVideo.PlayDuration = int64(sleepTime)
}
fallthrough
case VideoType_Live:
// 直播
crawler.LiveCount++
log.Info().Interface("video", currentVideo).Msg(FOUND_LIVE_SUCCESS)
// wait 3s for live loading
time.Sleep(3 * time.Second)
// take screenshot and get screen texts by OCR
screenResult, err := crawler.driverExt.GetScreenResult(
WithScreenShotOCR(true),
WithScreenShotUpload(true),
WithScreenShotLiveType(true),
)
if err != nil {
log.Error().Err(err).Msg("get screen result failed")
}
// add live type
if screenResult.ImageResult != nil &&
screenResult.ImageResult.LiveType != "" &&
screenResult.ImageResult.LiveType != "NoLive" {
currentVideo.LiveType = screenResult.ImageResult.LiveType
}
// simulation watch live video
simulationPlayDuration := math.Min(float64(currentVideo.PlayDuration), 300000)
sleepStrict(swipeFinishTime, int64(simulationPlayDuration))
screenResult.Video = currentVideo
screenResult.Resolution = dExt.WindowSize
screenResult.SwipeStartTime = swipeStartTime.UnixMilli()
screenResult.SwipeFinishTime = swipeFinishTime.UnixMilli()
screenResult.TotalElapsed = time.Since(swipeFinishTime).Milliseconds()
screenResult.FetchVideoStartTime = fetchVideoStartTime.UnixMilli()
screenResult.FetchVideoFinishTime = fetchVideoFinishTime.UnixMilli()
screenResult.FetchVideoElapsed = fetchVideoFinishTime.Sub(fetchVideoStartTime).Milliseconds()
var exitLive bool
if crawler.isLiveTargetAchieved() {
log.Info().Interface("live", currentVideo).
Msg("live count achieved, exit live room")
exitLive = true
} else if rand.Float64() <= livePreviewProb {
log.Info().Interface("livePreviewProb", livePreviewProb).Msg("exit live room by preview live chance")
exitLive = true
}
// isFeed通过预览流进入内流失败的情况下防止使用退出直播间逻辑影响首次进入内流至少会消费两个直播间才能退出
if !isFeed && exitLive && currentVideo.Type == VideoType_Live {
err = crawler.exitLiveRoom()
if err != nil {
if errors.Is(err, code.TimeoutError) || errors.Is(err, code.InterruptError) {
return err
}
log.Error().Err(err).Msg("run live crawler failed, continue")
}
} else {
isFeed = false
}
default:
isFeed = true
// 点播 || 图文 || 广告 || etc.
crawler.FeedCount++
log.Info().Interface("video", currentVideo).Msg(FOUND_FEED_SUCCESS)
screenResult := &ScreenResult{
Resolution: dExt.WindowSize,
Video: currentVideo,
// log swipe timelines
SwipeStartTime: swipeStartTime.UnixMilli(),
SwipeFinishTime: swipeFinishTime.UnixMilli(),
FetchVideoStartTime: fetchVideoStartTime.UnixMilli(),
FetchVideoFinishTime: fetchVideoFinishTime.UnixMilli(),
FetchVideoElapsed: fetchVideoFinishTime.Sub(fetchVideoStartTime).Milliseconds(),
}
dExt.cacheStepData.screenResults[time.Now().String()] = screenResult
// simulation watch feed video
simulationPlayDuration := math.Min(float64(currentVideo.PlayDuration), 600000)
sleepStrict(swipeFinishTime, int64(simulationPlayDuration))
screenResult.TotalElapsed = time.Since(swipeFinishTime).Milliseconds()
}
// check if target count achieved
if crawler.isTargetAchieved() {
log.Info().Msg("target count achieved, exit crawler")
return nil
}
// reset failed count
crawler.failedCount = 0
}
}
}
type VideoType string
const (
VideoType_Feed VideoType = "FEED"
VideoType_PreviewLive VideoType = "PREVIEW-LIVE" // 直播预览流
VideoType_Live VideoType = "LIVE"
VideoType_Image VideoType = "IMAGE"
VideoType_AD VideoType = "AD"
)
type Video struct {
Type VideoType `json:"type" required:"true"` // 视频类型, feed/preview-live/live/image
DataType string `json:"data_type"` // 数据源对应的事件名称
// Feed 视频基础数据
CacheKey string `json:"cache_key,omitempty"` // cachekey
VideoID string `json:"video_id,omitempty"` // 视频 video ID
URL string `json:"feed_url,omitempty"` // 实际播放的视频 url
UserName string `json:"user_name"` // 视频作者
Duration int64 `json:"duration,omitempty"` // 视频时长(ms)
Caption string `json:"caption,omitempty"` // 视频文案
// 作者信息
UserID string `json:"user_id"` // 作者用户名
FollowerCount int64 `json:"follower_count"` // 作者粉丝数
// 视频热度数据
ViewCount int64 `json:"view_count,omitempty"` // feed 观看数
LikeCount int64 `json:"like_count,omitempty"` // feed 点赞数
CommentCount int64 `json:"comment_count,omitempty"` // feed 评论数
CollectCount int64 `json:"collect_count,omitempty"` // feed 收藏数
ForwardCount int64 `json:"forward_count,omitempty"` // feed 转发数
ShareCount int64 `json:"share_count,omitempty"` // feed 分享数
// timelines
PublishTimestamp int64 `json:"publish_timestamp,omitempty"` // feed 发布时间戳
PreloadTimestamp int64 `json:"preload_timestamp,omitempty"` // feed 预加载时间戳
// Live 视频基础数据
LiveStreamID string `json:"live_stream_id,omitempty"` // 直播流 ID
LiveStreamURL string `json:"live_stream_url,omitempty"` // 直播流地址
LiveType string `json:"live_type,omitempty"` // 直播间类型
// 网络数据
ThroughputKbps int64 `json:"throughput_kbps,omitempty"` // 网速
// 视频热度数据
AudienceCount int64 `json:"audience_count,omitempty"` // 直播间人数
// 图文数据
ImageUrls []string `json:"image_urls,omitempty"` // 图片对应的 url 列表
// 记录仿真决策信息
PlayDuration int64 `json:"play_duration"` // 播放时长(ms),取自 Simulation/Random
SimulationPlayProgress float64 `json:"simulation_play_progress"` // 仿真播放比例(完播率)
SimulationPlayDuration int64 `json:"simulation_play_duration"` // 仿真播放时长(ms)
RandomPlayDuration int64 `json:"random_play_duration"` // 随机播放时长(ms)
}
func (vc *VideoCrawler) clearCurrentVideo() error {
if !vc.driverExt.plugin.Has("ClearCurrentVideo") {
return errors.New("plugin missing ClearCurrentVideo method")
}
_, err := vc.driverExt.plugin.Call("ClearCurrentVideo")
if err != nil {
return errors.Wrap(err, "call plugin ClearCurrentVideo failed")
}
return nil
}
func (vc *VideoCrawler) getCurrentVideo() (video *Video, err error) {
if !vc.driverExt.plugin.Has("GetCurrentVideo") {
return nil, errors.New("plugin missing GetCurrentVideo method")
}
resp, err := vc.driverExt.plugin.Call("GetCurrentVideo")
if err != nil {
return nil, errors.Wrap(err, "call plugin GetCurrentVideo failed")
}
if resp == nil {
return nil, errors.New("video not found")
}
feedBytes, err := json.Marshal(resp)
if err != nil {
return nil, errors.New("json marshal video info failed")
}
video = &Video{}
err = json.Unmarshal(feedBytes, video)
if err != nil {
return nil, errors.Wrap(err, "json unmarshal video info failed")
}
if video.Type == VideoType_Live || video.Type == VideoType_PreviewLive {
video.RandomPlayDuration = getSimulationDuration(vc.configs.Live.SleepRandom)
} else {
video.RandomPlayDuration = getSimulationDuration(vc.configs.Feed.SleepRandom)
}
// get simulation play duration
if video.SimulationPlayDuration != 0 {
video.PlayDuration = video.SimulationPlayDuration
} else {
video.PlayDuration = video.RandomPlayDuration
}
log.Info().
Str("type", string(video.Type)).
Str("dataType", video.DataType).
Msg("get current video success")
return video, nil
}
func (vc *VideoCrawler) getLivePreviewProb() float64 {
if vc.driverExt.Device.System() == "ios" {
return 0.5326
} else if vc.driverExt.Device.System() == "android" {
return 0.3414
}
return -1
}

View File

@@ -1,33 +0,0 @@
//go:build localtest
package uixt
import (
"testing"
)
func TestVideoCrawler(t *testing.T) {
setupAndroid(t)
driverExt.Driver.AppLaunch("com.ss.android.ugc.aweme")
configs := &VideoCrawlerConfigs{
Timeout: 600,
Feed: FeedConfig{
TargetCount: 5,
TargetLabels: []TargetLabel{
{Text: `^广告$`, Scope: Scope{0, 0.5, 1, 1}, Regex: true},
{Text: `^图文$`, Scope: Scope{0, 0.5, 1, 1}, Regex: true, Target: 2},
{Text: `^特效\|`, Scope: Scope{0, 0.5, 1, 1}, Regex: true},
{Text: `^模板\|`, Scope: Scope{0, 0.5, 1, 1}, Regex: true},
{Text: `^购物\|`, Scope: Scope{0, 0.5, 1, 1}, Regex: true},
},
SleepRandom: []interface{}{0, 5, 0.7, 5, 10, 0.3},
},
Live: LiveConfig{
TargetCount: 3,
SleepRandom: []interface{}{15, 20},
},
}
err := driverExt.VideoCrawler(configs)
checkErr(t, err)
}

View File

@@ -337,15 +337,6 @@ func (s *StepMobile) SleepRandom(params ...float64) *StepMobile {
return &StepMobile{step: s.step}
}
func (s *StepMobile) VideoCrawler(params map[string]interface{}) *StepMobile {
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
Method: uixt.ACTION_VideoCrawler,
Params: params,
Options: nil,
})
return &StepMobile{step: s.step}
}
func (s *StepMobile) EndToEndDelay(options ...uixt.ActionOption) *StepMobile {
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
Method: uixt.ACTION_EndToEndDelay,
@@ -651,7 +642,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
// automatic handling of pop-up windows on each step finished
if !step.IgnorePopup && !s.IgnorePopup() {
if err2 := uiDriver.ClosePopups(); err2 != nil {
if err2 := uiDriver.ClosePopupsHandler(); err2 != nil {
log.Error().Err(err2).Str("step", step.Name).Msg("auto handle popup failed")
}
}