From e3596a6051a43d246ad4a37f65b1cace451eea85 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 23 Dec 2022 12:03:49 +0800 Subject: [PATCH 1/9] refactor: convert postman case --- hrp/cmd/convert.go | 114 ++++--- hrp/cmd/curl.go | 98 ------ hrp/convert.go | 1 + hrp/pkg/convert/README.md | 31 +- hrp/pkg/convert/converter.go | 278 ------------------ hrp/pkg/convert/from_ab.go | 1 + hrp/pkg/convert/from_gotest.go | 4 +- hrp/pkg/convert/from_jmeter.go | 1 + hrp/pkg/convert/from_json.go | 3 +- hrp/pkg/convert/from_postman.go | 2 +- hrp/pkg/convert/from_yaml.go | 2 +- hrp/pkg/convert/main.go | 205 +++++++++++++ .../{converter_test.go => main_test.go} | 36 +-- hrp/pkg/convert/to_gotest.go | 6 + hrp/pkg/convert/to_json.go | 13 + hrp/pkg/convert/to_pytest.go | 22 ++ hrp/pkg/convert/to_yaml.go | 13 + 17 files changed, 371 insertions(+), 459 deletions(-) delete mode 100644 hrp/cmd/curl.go create mode 100644 hrp/convert.go delete mode 100644 hrp/pkg/convert/converter.go create mode 100644 hrp/pkg/convert/from_ab.go create mode 100644 hrp/pkg/convert/from_jmeter.go create mode 100644 hrp/pkg/convert/main.go rename hrp/pkg/convert/{converter_test.go => main_test.go} (77%) create mode 100644 hrp/pkg/convert/to_gotest.go create mode 100644 hrp/pkg/convert/to_json.go create mode 100644 hrp/pkg/convert/to_pytest.go create mode 100644 hrp/pkg/convert/to_yaml.go diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index 5ec00480..e2a73e6e 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -1,7 +1,6 @@ package cmd import ( - "errors" "fmt" "github.com/rs/zerolog/log" @@ -13,65 +12,86 @@ import ( ) var convertCmd = &cobra.Command{ - Use: "convert $path...", - Short: "convert to JSON/YAML/gotest/pytest testcases", - Args: cobra.MinimumNArgs(1), + Use: "convert $path...", + Short: "convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases", + Args: cobra.MinimumNArgs(1), + SilenceUsage: false, PreRun: func(cmd *cobra.Command, args []string) { setLogLevel(logLevel) }, - RunE: convertRun, + RunE: func(cmd *cobra.Command, args []string) error { + caseConverter := convert.NewConverter(outputDir, profilePath) + + var fromType convert.FromType + if fromYAMLFlag { + fromType = convert.FromTypeYAML + } else if fromPostmanFlag { + fromType = convert.FromTypePostman + } else if fromHARFlag { + fromType = convert.FromTypeHAR + } else { + fromType = convert.FromTypeJSON + log.Info().Str("fromType", fromType.String()).Msg("set default") + } + + var outputType convert.OutputType + if toYAMLFlag { + outputType = convert.OutputTypeYAML + } else if toPyTestFlag { + packages := []string{ + fmt.Sprintf("httprunner==%s", version.HttpRunnerMinimumVersion), + } + _, err := myexec.EnsurePython3Venv(venv, packages...) + if err != nil { + log.Error().Err(err).Msg("python3 venv is not ready") + return err + } + + outputType = convert.OutputTypePyTest + } else { + outputType = convert.OutputTypeJSON + log.Info().Str("outputType", outputType.String()).Msg("set default") + } + + for _, arg := range args { + if err := caseConverter.Convert(arg, fromType, outputType); err != nil { + log.Error().Err(err).Str("path", arg). + Str("outputType", outputType.String()). + Msg("convert case failed") + return err + } + } + + return nil + }, } var ( + outputDir string + profilePath string + + fromJSONFlag bool + fromYAMLFlag bool + fromPostmanFlag bool + fromHARFlag bool + toJSONFlag bool toYAMLFlag bool - toGoTestFlag bool toPyTestFlag bool - outputDir string - profilePath string - - outputType convert.OutputType ) func init() { rootCmd.AddCommand(convertCmd) + + convertCmd.Flags().BoolVar(&fromJSONFlag, "from-json", true, "load from json case format") + convertCmd.Flags().BoolVar(&fromYAMLFlag, "from-yaml", false, "load from yaml case format") + convertCmd.Flags().BoolVar(&fromHARFlag, "from-har", false, "load from HAR format") + convertCmd.Flags().BoolVar(&fromPostmanFlag, "from-postman", false, "load from postman format") + + convertCmd.Flags().BoolVar(&toJSONFlag, "to-json", true, "convert to JSON case scripts") + convertCmd.Flags().BoolVar(&toYAMLFlag, "to-yaml", false, "convert to YAML case scripts") convertCmd.Flags().BoolVar(&toPyTestFlag, "to-pytest", false, "convert to pytest scripts") - convertCmd.Flags().BoolVar(&toGoTestFlag, "to-gotest", false, "convert to gotest scripts (TODO)") - convertCmd.Flags().BoolVar(&toJSONFlag, "to-json", false, "convert to JSON scripts (default)") - convertCmd.Flags().BoolVar(&toYAMLFlag, "to-yaml", false, "convert to YAML scripts") - convertCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") + + convertCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory") convertCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies") } - -func convertRun(cmd *cobra.Command, args []string) error { - var flagCount int - if toJSONFlag { - flagCount++ - } - if toYAMLFlag { - flagCount++ - outputType = convert.OutputTypeYAML - } - if toGoTestFlag { - flagCount++ - outputType = convert.OutputTypeGoTest - } - if toPyTestFlag { - flagCount++ - outputType = convert.OutputTypePyTest - - packages := []string{ - fmt.Sprintf("httprunner==%s", version.HttpRunnerMinimumVersion), - } - _, err := myexec.EnsurePython3Venv(venv, packages...) - if err != nil { - log.Error().Err(err).Msg("python3 venv is not ready") - return err - } - } - if flagCount > 1 { - return errors.New("please specify at most one conversion flag") - } - convert.Run(outputType, outputDir, profilePath, args) - return nil -} diff --git a/hrp/cmd/curl.go b/hrp/cmd/curl.go deleted file mode 100644 index a75ae84b..00000000 --- a/hrp/cmd/curl.go +++ /dev/null @@ -1,98 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "strings" - - "github.com/rs/zerolog/log" - "github.com/spf13/cobra" - - "github.com/httprunner/httprunner/v4/hrp" - "github.com/httprunner/httprunner/v4/hrp/internal/env" - "github.com/httprunner/httprunner/v4/hrp/pkg/boomer" - "github.com/httprunner/httprunner/v4/hrp/pkg/convert" -) - -var runCurlCmd = &cobra.Command{ - Use: "curl URLs", - Short: "run API test with curl command", - Args: cobra.MinimumNArgs(1), - DisableFlagParsing: true, - PreRun: func(cmd *cobra.Command, args []string) { - setLogLevel(logLevel) - }, - RunE: func(cmd *cobra.Command, args []string) error { - runner := makeHRPRunner() - return runner.Run(makeCurlTestCase(args)) - }, -} - -var boomCurlCmd = &cobra.Command{ - Use: "curl URLs", - Short: "run load test with curl command", - Args: cobra.MinimumNArgs(1), - DisableFlagParsing: true, - PreRun: func(cmd *cobra.Command, args []string) { - boomer.SetUlimit(10240) - if !strings.EqualFold(logLevel, "DEBUG") { - logLevel = "WARN" // disable info logs for load testing - } - setLogLevel(logLevel) - }, - RunE: func(cmd *cobra.Command, args []string) error { - boomer, err := makeHRPBoomer() - if err != nil { - return err - } - boomer.Run(makeCurlTestCase(args)) - return nil - }, -} - -var convertCurlCmd = &cobra.Command{ - Use: "curl URLs", - Short: "convert curl command to httprunner testcase", - Args: cobra.MinimumNArgs(1), - DisableFlagParsing: true, - PreRun: func(cmd *cobra.Command, args []string) { - setLogLevel(logLevel) - }, - RunE: func(cmd *cobra.Command, args []string) error { - curlCommand := makeCurlCommand(args) - return convertRun(cmd, []string{curlCommand}) - }, -} - -func init() { - runCmd.AddCommand(runCurlCmd) - boomCmd.AddCommand(boomCurlCmd) - convertCmd.AddCommand(convertCurlCmd) -} - -func makeCurlTestCase(args []string) *hrp.TestCase { - curlCommand := makeCurlCommand(args) - tCase, err := convert.LoadSingleCurlCase(curlCommand) - if err != nil { - log.Error().Err(err).Msg("convert curl command failed") - os.Exit(1) - } - testCase, err := tCase.ToTestCase(env.RootDir) - if err != nil { - log.Error().Err(err).Msg("convert testcase to failed") - os.Exit(1) - } - return testCase -} - -func makeCurlCommand(args []string) string { - for i := 0; i < len(args); i++ { - if !strings.HasPrefix(args[i], "-") { - args[i] = fmt.Sprintf("\"%s\"", args[i]) - } - } - var curlCmd []string - curlCmd = append(curlCmd, "curl") - curlCmd = append(curlCmd, args...) - return strings.Join(curlCmd, " ") -} diff --git a/hrp/convert.go b/hrp/convert.go new file mode 100644 index 00000000..d10a1454 --- /dev/null +++ b/hrp/convert.go @@ -0,0 +1 @@ +package hrp diff --git a/hrp/pkg/convert/README.md b/hrp/pkg/convert/README.md index 90c0314f..56e8b3ad 100644 --- a/hrp/pkg/convert/README.md +++ b/hrp/pkg/convert/README.md @@ -4,32 +4,36 @@ ```shell $ hrp convert -h -convert to JSON/YAML/gotest/pytest testcases +convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases Usage: hrp convert $path... [flags] Flags: + --from-har load from HAR format + --from-json load from json case format (default true) + --from-postman load from postman format + --from-yaml load from yaml case format -h, --help help for convert - -d, --output-dir string specify output directory, default to the same dir with har file - -p, --profile string specify profile path to override headers (except for auto-generated headers) and cookies - --to-gotest convert to gotest scripts (TODO) - --to-json convert to JSON scripts (default true) + -d, --output-dir string specify output directory + -p, --profile string specify profile path to override headers and cookies + --to-json convert to JSON case scripts (default true) --to-pytest convert to pytest scripts - --to-yaml convert to YAML scripts + --to-yaml convert to YAML case scripts Global Flags: --log-json set log to json format -l, --log-level string set log level (default "INFO") + --venv string specify python3 venv path ``` -`hrp convert` 指令用于将 HAR/Postman/JMeter/Swagger 文件或 curl/Apache ab 指令转化为 JSON/YAML/gotest/pytest 形态的测试用例,同时也支持测试用例各个形态之间的相互转化。 +`hrp convert` 指令用于将 HAR/Postman/JMeter/Swagger 文件或 curl/Apache ab 指令转化为 HttpRunner JSON/YAML/gotest/pytest 形态的测试用例,同时也支持 HttpRunner 测试用例各个形态之间的相互转化。 该指令所有选项的详细说明如下: -1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入转化为对应形态的测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例 -2. `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹 -3. `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例: +- `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入转化为对应形态的 HttpRunner 测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例 +- `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹;默认输出的文件夹为源文件所在的文件夹 +- `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例: - 根据 profile 替换指定的 `Headers` 和 `Cookies` 信息 @@ -52,10 +56,9 @@ cookies: ## 注意事项 -1. 输出的测试用例文件名格式为 `Postman 工程文件名称(不带拓展名)` + `_test` + `.json/.yaml/.go/.py 后缀`,如果该文件已经存在则会进行覆盖 -2. `hrp convert` 可以自动识别输入类型,因此不需要通过选项来手动制定输入类型,如遇到无法识别、不支持或转换失败的情况,则会输出错误日志并跳过,不会影响其他转换过程的正常进行 -3. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式 -4. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格 +1. 输出的测试用例文件名格式为 `源文件名称(不带拓展名)` + `_test` + `.json/.yaml/.go/.py 后缀`,如果该文件已经存在则会进行覆盖 +2. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式 +3. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格 ## 转换流程图 diff --git a/hrp/pkg/convert/converter.go b/hrp/pkg/convert/converter.go deleted file mode 100644 index c849df65..00000000 --- a/hrp/pkg/convert/converter.go +++ /dev/null @@ -1,278 +0,0 @@ -package convert - -import ( - _ "embed" - "fmt" - "path/filepath" - "strings" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/v4/hrp" - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" - "github.com/httprunner/httprunner/v4/hrp/internal/env" - "github.com/httprunner/httprunner/v4/hrp/internal/myexec" - "github.com/httprunner/httprunner/v4/hrp/internal/sdk" -) - -// target testcase format extensions -const ( - suffixJSON = ".json" - suffixYAML = ".yaml" - suffixGoTest = ".go" - suffixPyTest = ".py" -) - -type OutputType int - -const ( - OutputTypeJSON OutputType = iota // default output type: JSON - OutputTypeYAML - OutputTypeGoTest - OutputTypePyTest -) - -func (outputType OutputType) String() string { - switch outputType { - case OutputTypeYAML: - return "yaml" - case OutputTypeGoTest: - return "gotest" - case OutputTypePyTest: - return "pytest" - default: - return "json" - } -} - -// Profile is used to override or update(create if not existed) original headers and cookies -type Profile struct { - Override bool `json:"override" yaml:"override"` - Headers map[string]string `json:"headers" yaml:"headers"` - Cookies map[string]string `json:"cookies" yaml:"cookies"` -} - -func Run(outputType OutputType, outputDir, profilePath string, args []string) { - // report event - sdk.SendEvent(sdk.EventTracking{ - Category: "ConvertTests", - Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()), - }) - - var outputFiles []string - for _, inputSample := range args { - // loads source file and convert to TCase format - tCase, err := LoadTCase(inputSample) - if err != nil { - log.Warn().Err(err).Str("input sample", inputSample).Msg("convert input sample failed") - continue - } - - caseConverter := &TCaseConverter{ - InputSample: inputSample, - OutputDir: outputDir, - TCase: tCase, - } - - // override TCase with profile - if profilePath != "" { - caseConverter.overrideWithProfile(profilePath) - } - - // convert TCase format to target case format - var outputFile string - switch outputType { - case OutputTypeYAML: - outputFile, err = caseConverter.ToYAML() - case OutputTypeGoTest: - outputFile, err = caseConverter.ToGoTest() - case OutputTypePyTest: - outputFile, err = caseConverter.ToPyTest() - default: - outputFile, err = caseConverter.ToJSON() - } - if err != nil { - log.Error().Err(err). - Str("input sample", caseConverter.InputSample). - Msg("convert case failed") - continue - } - outputFiles = append(outputFiles, outputFile) - } - log.Info().Strs("output files", outputFiles).Msg("conversion completed") -} - -// LoadTCase loads source file and convert to TCase type -func LoadTCase(inputSample string) (*hrp.TCase, error) { - if strings.HasPrefix(inputSample, "curl ") { - // 'path' contains curl command - curlCase, err := LoadSingleCurlCase(inputSample) - if err != nil { - return nil, err - } - return curlCase, nil - } - extName := filepath.Ext(inputSample) - if extName == "" { - return nil, errors.New("file extension is not specified") - } - switch extName { - case ".har": - tCase, err := LoadHARCase(inputSample) - if err != nil { - return nil, err - } - return tCase, nil - case ".json": - // priority: hrp JSON case > postman > swagger - // check if hrp JSON case - tCase, err := LoadJSONCase(inputSample) - if err == nil { - return tCase, nil - } - - // check if postman format - casePostman, err := LoadPostmanCase(inputSample) - if err == nil { - return casePostman, nil - } - - // check if swagger format - caseSwagger, err := LoadSwaggerCase(inputSample) - if err == nil { - return caseSwagger, nil - } - - return nil, errors.New("unexpected JSON format") - case ".yaml", ".yml": - // priority: hrp YAML case > swagger - // check if hrp YAML case - tCase, err := NewYAMLCase(inputSample) - if err == nil { - return tCase, nil - } - - // check if swagger format - caseSwagger, err := LoadSwaggerCase(inputSample) - if err == nil { - return caseSwagger, nil - } - - return nil, errors.New("unexpected YAML format") - case ".go": // TODO - return nil, errors.New("convert gotest is not implemented") - case ".py": // TODO - return nil, errors.New("convert pytest is not implemented") - case ".jmx": // TODO - return nil, errors.New("convert JMeter jmx is not implemented") - case ".txt": - curlCase, err := LoadCurlCase(inputSample) - if err != nil { - return nil, err - } - return curlCase, nil - } - - return nil, fmt.Errorf("unsupported file type: %v", extName) -} - -// TCaseConverter holds the common properties of case converter -type TCaseConverter struct { - InputSample string - OutputDir string - TCase *hrp.TCase -} - -func (c *TCaseConverter) genOutputPath(suffix string) string { - var outFileFullName string - if curlCmd := strings.TrimSpace(c.InputSample); strings.HasPrefix(curlCmd, "curl ") { - outFileFullName = fmt.Sprintf("curl_%v_test%v", env.StartTimeStr, suffix) - if c.OutputDir != "" { - return filepath.Join(c.OutputDir, outFileFullName) - } else { - return filepath.Join(env.RootDir, outFileFullName) - } - } - outFileFullName = builtin.GetFileNameWithoutExtension(c.InputSample) + "_test" + suffix - if c.OutputDir != "" { - return filepath.Join(c.OutputDir, outFileFullName) - } else { - return filepath.Join(filepath.Dir(c.InputSample), outFileFullName) - } - // TODO avoid outFileFullName conflict? -} - -// convert TCase to pytest case -func (c *TCaseConverter) ToPyTest() (string, error) { - jsonPath, err := c.ToJSON() - if err != nil { - return "", errors.Wrap(err, "convert to JSON case failed") - } - - args := append([]string{"make"}, jsonPath) - err = myexec.ExecPython3Command("httprunner", args...) - if err != nil { - return "", err - } - return c.genOutputPath(suffixPyTest), nil -} - -// TODO: convert TCase to gotest case -func (c *TCaseConverter) ToGoTest() (string, error) { - return "", nil -} - -// convert TCase to JSON case -func (c *TCaseConverter) ToJSON() (string, error) { - jsonPath := c.genOutputPath(suffixJSON) - err := builtin.Dump2JSON(c.TCase, jsonPath) - if err != nil { - return "", err - } - return jsonPath, nil -} - -// convert TCase to YAML case -func (c *TCaseConverter) ToYAML() (string, error) { - yamlPath := c.genOutputPath(suffixYAML) - err := builtin.Dump2YAML(c.TCase, yamlPath) - if err != nil { - return "", err - } - return yamlPath, nil -} - -func (c *TCaseConverter) overrideWithProfile(path string) error { - log.Info().Str("path", path).Msg("load profile") - profile := new(Profile) - err := builtin.LoadFile(path, profile) - if err != nil { - log.Warn().Str("path", path). - Msg("failed to load profile, ignore!") - return err - } - - log.Info().Interface("profile", profile).Msg("override with profile") - for _, step := range c.TCase.TestSteps { - // override original headers and cookies - if profile.Override { - step.Request.Headers = make(map[string]string) - step.Request.Cookies = make(map[string]string) - } - // update (create if not existed) original headers and cookies - if step.Request.Headers == nil { - step.Request.Headers = make(map[string]string) - } - if step.Request.Cookies == nil { - step.Request.Cookies = make(map[string]string) - } - for k, v := range profile.Headers { - step.Request.Headers[k] = v - } - for k, v := range profile.Cookies { - step.Request.Cookies[k] = v - } - } - return nil -} diff --git a/hrp/pkg/convert/from_ab.go b/hrp/pkg/convert/from_ab.go new file mode 100644 index 00000000..233bcded --- /dev/null +++ b/hrp/pkg/convert/from_ab.go @@ -0,0 +1 @@ +package convert diff --git a/hrp/pkg/convert/from_gotest.go b/hrp/pkg/convert/from_gotest.go index eecde5a5..d25dff85 100644 --- a/hrp/pkg/convert/from_gotest.go +++ b/hrp/pkg/convert/from_gotest.go @@ -31,9 +31,9 @@ func convert2GoTestScripts(paths ...string) error { for _, testCase := range testCases { tc := testCase.ToTCase() converter := TCaseConverter{ - TCase: tc, + tCase: tc, } - pytestPath, err := converter.ToPyTest() + pytestPath, err := converter.toPyTest() if err != nil { log.Error().Err(err). Str("originPath", tc.Config.Path). diff --git a/hrp/pkg/convert/from_jmeter.go b/hrp/pkg/convert/from_jmeter.go new file mode 100644 index 00000000..233bcded --- /dev/null +++ b/hrp/pkg/convert/from_jmeter.go @@ -0,0 +1 @@ +package convert diff --git a/hrp/pkg/convert/from_json.go b/hrp/pkg/convert/from_json.go index 8e40f88b..b1c05ac5 100644 --- a/hrp/pkg/convert/from_json.go +++ b/hrp/pkg/convert/from_json.go @@ -2,13 +2,14 @@ package convert import ( "github.com/pkg/errors" + "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp" "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) func LoadJSONCase(path string) (*hrp.TCase, error) { - // load json case file + log.Info().Str("path", path).Msg("load json case file") caseJSON := new(hrp.TCase) err := builtin.LoadFile(path, caseJSON) if err != nil { diff --git a/hrp/pkg/convert/from_postman.go b/hrp/pkg/convert/from_postman.go index 9dbbfa7c..bd8e1527 100644 --- a/hrp/pkg/convert/from_postman.go +++ b/hrp/pkg/convert/from_postman.go @@ -113,7 +113,7 @@ var contentTypeMap = map[string]string{ } func LoadPostmanCase(path string) (*hrp.TCase, error) { - // load postman file + log.Info().Str("path", path).Msg("load postman case file") casePostman, err := loadCasePostman(path) if err != nil { return nil, err diff --git a/hrp/pkg/convert/from_yaml.go b/hrp/pkg/convert/from_yaml.go index 977e47de..b96f0a2d 100644 --- a/hrp/pkg/convert/from_yaml.go +++ b/hrp/pkg/convert/from_yaml.go @@ -9,7 +9,7 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) -func NewYAMLCase(path string) (*hrp.TCase, error) { +func LoadYAMLCase(path string) (*hrp.TCase, error) { // load yaml case file caseJSON := new(hrp.TCase) err := builtin.LoadFile(path, caseJSON) diff --git a/hrp/pkg/convert/main.go b/hrp/pkg/convert/main.go new file mode 100644 index 00000000..602489fe --- /dev/null +++ b/hrp/pkg/convert/main.go @@ -0,0 +1,205 @@ +package convert + +import ( + _ "embed" + "fmt" + "path/filepath" + + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" +) + +// target testcase format extensions +const ( + suffixJSON = ".json" + suffixYAML = ".yaml" + suffixGoTest = ".go" + suffixPyTest = ".py" +) + +type FromType int + +const ( + FromTypeJSON FromType = iota + FromTypeYAML + FromTypeHAR + FromTypePostman + FromTypeCurl + FromTypeSwagger + FromTypePyest + FromTypeGotest +) + +func (fromType FromType) String() string { + switch fromType { + case FromTypeYAML: + return "yaml" + case FromTypeHAR: + return "har" + case FromTypePostman: + return "postman" + case FromTypeSwagger: + return "swagger" + case FromTypeCurl: + return "curl" + case FromTypeGotest: + return "gotest" + case FromTypePyest: + return "pytest" + default: + return "json" + } +} + +type OutputType int + +const ( + OutputTypeJSON OutputType = iota // default output type: JSON + OutputTypeYAML + OutputTypeGoTest + OutputTypePyTest +) + +func (outputType OutputType) String() string { + switch outputType { + case OutputTypeYAML: + return "yaml" + case OutputTypeGoTest: + return "gotest" + case OutputTypePyTest: + return "pytest" + default: + return "json" + } +} + +// Profile is used to override or update(create if not existed) original headers and cookies +type Profile struct { + Override bool `json:"override" yaml:"override"` + Headers map[string]string `json:"headers" yaml:"headers"` + Cookies map[string]string `json:"cookies" yaml:"cookies"` +} + +func NewConverter(outputDir, profilePath string) *TCaseConverter { + return &TCaseConverter{ + profilePath: profilePath, + outputDir: outputDir, + } +} + +// TCaseConverter holds the common properties of case converter +type TCaseConverter struct { + fromFile string + profilePath string + outputDir string + tCase *hrp.TCase +} + +// LoadCase loads source file and convert to TCase type +func (c *TCaseConverter) loadCase(casePath string, fromType FromType) error { + c.fromFile = casePath + var err error + switch fromType { + case FromTypeJSON: + c.tCase, err = LoadJSONCase(casePath) + case FromTypeYAML: + c.tCase, err = LoadYAMLCase(casePath) + case FromTypeHAR: + c.tCase, err = LoadHARCase(casePath) + case FromTypePostman: + c.tCase, err = LoadPostmanCase(casePath) + case FromTypeSwagger: + c.tCase, err = LoadSwaggerCase(casePath) + case FromTypeCurl: + c.tCase, err = LoadCurlCase(casePath) + } + return err +} + +func (c *TCaseConverter) Convert(casePath string, fromType FromType, outputType OutputType) error { + // report event + sdk.SendEvent(sdk.EventTracking{ + Category: "ConvertTests", + Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()), + }) + log.Info().Str("path", casePath). + Str("fromType", fromType.String()). + Str("outputType", outputType.String()). + Msg("convert testcase") + + // load source file + err := c.loadCase(casePath, fromType) + if err != nil { + return err + } + + // override TCase with profile + if c.profilePath != "" { + c.overrideWithProfile(c.profilePath) + } + + // convert to target format + var outputFile string + switch outputType { + case OutputTypeYAML: + outputFile, err = c.toYAML() + case OutputTypeGoTest: + outputFile, err = c.toGoTest() + case OutputTypePyTest: + outputFile, err = c.toPyTest() + default: + outputFile, err = c.toJSON() + } + if err != nil { + return err + } + + log.Info().Str("outputFile", outputFile).Msg("conversion completed") + return nil +} + +func (c *TCaseConverter) genOutputPath(suffix string) string { + outFileFullName := builtin.GetFileNameWithoutExtension(c.fromFile) + "_test" + suffix + if c.outputDir != "" { + return filepath.Join(c.outputDir, outFileFullName) + } else { + return filepath.Join(filepath.Dir(c.fromFile), outFileFullName) + } +} + +func (c *TCaseConverter) overrideWithProfile(path string) error { + log.Info().Str("path", path).Msg("load profile") + profile := new(Profile) + err := builtin.LoadFile(path, profile) + if err != nil { + log.Warn().Str("path", path). + Msg("failed to load profile, ignore!") + return err + } + + log.Info().Interface("profile", profile).Msg("override with profile") + for _, step := range c.tCase.TestSteps { + // override original headers and cookies + if profile.Override { + step.Request.Headers = make(map[string]string) + step.Request.Cookies = make(map[string]string) + } + // update (create if not existed) original headers and cookies + if step.Request.Headers == nil { + step.Request.Headers = make(map[string]string) + } + if step.Request.Cookies == nil { + step.Request.Cookies = make(map[string]string) + } + for k, v := range profile.Headers { + step.Request.Headers[k] = v + } + for k, v := range profile.Cookies { + step.Request.Cookies[k] = v + } + } + return nil +} diff --git a/hrp/pkg/convert/converter_test.go b/hrp/pkg/convert/main_test.go similarity index 77% rename from hrp/pkg/convert/converter_test.go rename to hrp/pkg/convert/main_test.go index 4c6c0f58..9ed3e432 100644 --- a/hrp/pkg/convert/converter_test.go +++ b/hrp/pkg/convert/main_test.go @@ -13,31 +13,33 @@ const ( profileOverridePath = "../../../examples/data/profile_override.yml" ) +var converter *TCaseConverter + +func init() { + converter = NewConverter("", "") +} + func TestLoadTCase(t *testing.T) { - tCase, err := LoadTCase(harPath) + err := converter.loadCase(harPath, FromTypeHAR) if !assert.NoError(t, err) { t.Fatal() } - if !assert.NotEmpty(t, tCase) { + if !assert.NotEmpty(t, converter.tCase) { t.Fatal() } } func TestLoadHARWithProfileOverride(t *testing.T) { - tCase, err := LoadTCase(harPath) + err := converter.loadCase(harPath, FromTypeHAR) if !assert.NoError(t, err) { t.Fatal() } - if !assert.NotEmpty(t, tCase) { + if !assert.NotEmpty(t, converter.tCase) { t.Fatal() } - caseConverter := &TCaseConverter{ - TCase: tCase, - } - // override TCase with profile - err = caseConverter.overrideWithProfile(profileOverridePath) + err = converter.overrideWithProfile(profileOverridePath) if !assert.NoError(t, err) { t.Fatal() } @@ -45,12 +47,12 @@ func TestLoadHARWithProfileOverride(t *testing.T) { for i := 0; i < 3; i++ { if !assert.Equal(t, map[string]string{"Content-Type": "application/x-www-form-urlencoded"}, - caseConverter.TCase.TestSteps[i].Request.Headers) { + converter.tCase.TestSteps[i].Request.Headers) { t.FailNow() } if !assert.Equal(t, map[string]string{"UserName": "debugtalk"}, - caseConverter.TCase.TestSteps[i].Request.Cookies) { + converter.tCase.TestSteps[i].Request.Cookies) { t.FailNow() } } @@ -58,7 +60,7 @@ func TestLoadHARWithProfileOverride(t *testing.T) { func TestMakeRequestWithProfile(t *testing.T) { caseConverter := &TCaseConverter{ - TCase: &hrp.TCase{ + tCase: &hrp.TCase{ TestSteps: []*hrp.TStep{ { Request: &hrp.Request{ @@ -87,19 +89,19 @@ func TestMakeRequestWithProfile(t *testing.T) { if !assert.Equal(t, map[string]string{ "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "hrp", - }, caseConverter.TCase.TestSteps[0].Request.Headers) { + }, caseConverter.tCase.TestSteps[0].Request.Headers) { t.Fatal() } if !assert.Equal(t, map[string]string{ "UserName": "debugtalk", "abc": "123", - }, caseConverter.TCase.TestSteps[0].Request.Cookies) { + }, caseConverter.tCase.TestSteps[0].Request.Cookies) { t.Fatal() } } func TestMakeRequestWithProfileOverride(t *testing.T) { caseConverter := &TCaseConverter{ - TCase: &hrp.TCase{ + tCase: &hrp.TCase{ TestSteps: []*hrp.TStep{ { Request: &hrp.Request{ @@ -129,12 +131,12 @@ func TestMakeRequestWithProfileOverride(t *testing.T) { if !assert.Equal(t, map[string]string{ "Content-Type": "application/x-www-form-urlencoded", - }, caseConverter.TCase.TestSteps[0].Request.Headers) { + }, caseConverter.tCase.TestSteps[0].Request.Headers) { t.Fatal() } if !assert.Equal(t, map[string]string{ "UserName": "debugtalk", - }, caseConverter.TCase.TestSteps[0].Request.Cookies) { + }, caseConverter.tCase.TestSteps[0].Request.Cookies) { t.Fatal() } } diff --git a/hrp/pkg/convert/to_gotest.go b/hrp/pkg/convert/to_gotest.go new file mode 100644 index 00000000..643c896a --- /dev/null +++ b/hrp/pkg/convert/to_gotest.go @@ -0,0 +1,6 @@ +package convert + +// TODO: convert TCase to gotest case +func (c *TCaseConverter) toGoTest() (string, error) { + return "", nil +} diff --git a/hrp/pkg/convert/to_json.go b/hrp/pkg/convert/to_json.go new file mode 100644 index 00000000..1fb47ed4 --- /dev/null +++ b/hrp/pkg/convert/to_json.go @@ -0,0 +1,13 @@ +package convert + +import "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + +// convert TCase to JSON case +func (c *TCaseConverter) toJSON() (string, error) { + jsonPath := c.genOutputPath(suffixJSON) + err := builtin.Dump2JSON(c.tCase, jsonPath) + if err != nil { + return "", err + } + return jsonPath, nil +} diff --git a/hrp/pkg/convert/to_pytest.go b/hrp/pkg/convert/to_pytest.go new file mode 100644 index 00000000..224974c3 --- /dev/null +++ b/hrp/pkg/convert/to_pytest.go @@ -0,0 +1,22 @@ +package convert + +import ( + "github.com/pkg/errors" + + "github.com/httprunner/httprunner/v4/hrp/internal/myexec" +) + +// convert TCase to pytest case +func (c *TCaseConverter) toPyTest() (string, error) { + jsonPath, err := c.toJSON() + if err != nil { + return "", errors.Wrap(err, "convert to JSON case failed") + } + + args := append([]string{"make"}, jsonPath) + err = myexec.ExecPython3Command("httprunner", args...) + if err != nil { + return "", err + } + return c.genOutputPath(suffixPyTest), nil +} diff --git a/hrp/pkg/convert/to_yaml.go b/hrp/pkg/convert/to_yaml.go new file mode 100644 index 00000000..0ada6511 --- /dev/null +++ b/hrp/pkg/convert/to_yaml.go @@ -0,0 +1,13 @@ +package convert + +import "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + +// convert TCase to YAML case +func (c *TCaseConverter) toYAML() (string, error) { + yamlPath := c.genOutputPath(suffixYAML) + err := builtin.Dump2YAML(c.tCase, yamlPath) + if err != nil { + return "", err + } + return yamlPath, nil +} From eeae8b29611128570957501e2709173cd4fd8f1e Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 23 Dec 2022 14:03:53 +0800 Subject: [PATCH 2/9] fix: convert postman body in json --- hrp/pkg/boomer/output_test.go | 2 +- hrp/pkg/convert/from_postman.go | 22 +++++----------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/hrp/pkg/boomer/output_test.go b/hrp/pkg/boomer/output_test.go index 58e3dee1..7a9d3c76 100644 --- a/hrp/pkg/boomer/output_test.go +++ b/hrp/pkg/boomer/output_test.go @@ -81,7 +81,7 @@ func TestConsoleOutput(t *testing.T) { data["stats"] = []interface{}{stat} stat["name"] = "http" - stat["method"] = "post" + stat["method"] = "POST" stat["num_requests"] = int64(100) stat["num_failures"] = int64(10) stat["response_times"] = map[int64]int64{ diff --git a/hrp/pkg/convert/from_postman.go b/hrp/pkg/convert/from_postman.go index bd8e1527..99774bea 100644 --- a/hrp/pkg/convert/from_postman.go +++ b/hrp/pkg/convert/from_postman.go @@ -330,29 +330,17 @@ func (s *stepFromPostman) makeRequestBodyRaw(item *TItem) (err error) { } }() - // extract language type, default languageType: text - languageType := "text" - iOptions := item.Request.Body.Options - if iOptions != nil { - iLanguage := iOptions.(map[string]interface{})["raw"] - if iLanguage != nil { - languageType = iLanguage.(map[string]interface{})["language"].(string) - } - } - - // make request body and indicate Content-Type - rawBody := item.Request.Body.Raw - if languageType == "json" { + s.Request.Body = item.Request.Body.Raw + contentType := s.Request.Headers["Content-Type"] + if strings.Contains(contentType, "application/json") { var iBody interface{} - err = json.Unmarshal([]byte(rawBody), &iBody) + err = json.Unmarshal([]byte(item.Request.Body.Raw), &iBody) if err != nil { + log.Warn().Err(err).Msg("33333") return errors.Wrap(err, "make request body (raw -> json) failed") } s.Request.Body = iBody - } else { - s.Request.Body = rawBody } - s.Request.Headers["Content-Type"] = contentTypeMap[languageType] return } From a55a2ff730b07f27ce69efde80da62edde674679 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 23 Dec 2022 14:04:22 +0800 Subject: [PATCH 3/9] fix: ensure HTTP request method in uppper case --- hrp/runner.go | 2 +- hrp/step_request.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hrp/runner.go b/hrp/runner.go index aa4665ea..9783b1ae 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -559,7 +559,7 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error { // check if failfast if r.caseRunner.hrpRunner.failfast { - return errors.Wrap(err, "abort running due to failfast setting") + return errors.New("abort running due to failfast setting") } } diff --git a/hrp/step_request.go b/hrp/step_request.go index d56536bd..12a8ffbb 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -317,7 +317,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err config := r.caseRunner.parsedConfig rb := newRequestBuilder(parser, config, step.Request) - rb.req.Method = string(step.Request.Method) + rb.req.Method = strings.ToUpper(string(step.Request.Method)) err = rb.prepareUrlParams(stepVariables) if err != nil { From 59088174de18ae191a6d60ccf5e1abbef2af9c14 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 23 Dec 2022 18:22:37 +0800 Subject: [PATCH 4/9] feat: load from curl text files --- hrp/cmd/convert.go | 4 ++++ hrp/internal/builtin/utils.go | 35 ----------------------------------- hrp/pkg/convert/from_curl.go | 34 ++++++++++++++++++++-------------- 3 files changed, 24 insertions(+), 49 deletions(-) diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index e2a73e6e..ac6617c9 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -29,6 +29,8 @@ var convertCmd = &cobra.Command{ fromType = convert.FromTypePostman } else if fromHARFlag { fromType = convert.FromTypeHAR + } else if fromCurlFlag { + fromType = convert.FromTypeCurl } else { fromType = convert.FromTypeJSON log.Info().Str("fromType", fromType.String()).Msg("set default") @@ -74,6 +76,7 @@ var ( fromYAMLFlag bool fromPostmanFlag bool fromHARFlag bool + fromCurlFlag bool toJSONFlag bool toYAMLFlag bool @@ -87,6 +90,7 @@ func init() { convertCmd.Flags().BoolVar(&fromYAMLFlag, "from-yaml", false, "load from yaml case format") convertCmd.Flags().BoolVar(&fromHARFlag, "from-har", false, "load from HAR format") convertCmd.Flags().BoolVar(&fromPostmanFlag, "from-postman", false, "load from postman format") + convertCmd.Flags().BoolVar(&fromCurlFlag, "from-curl", false, "load from curl format") convertCmd.Flags().BoolVar(&toJSONFlag, "to-json", true, "convert to JSON case scripts") convertCmd.Flags().BoolVar(&toYAMLFlag, "to-yaml", false, "convert to YAML case scripts") diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 2f109a5b..63dbfd0f 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -1,7 +1,6 @@ package builtin import ( - "bufio" "bytes" "crypto/hmac" "crypto/sha256" @@ -352,40 +351,6 @@ func ReadFile(path string) ([]byte, error) { return file, nil } -func ReadCmdLines(path string) ([]string, error) { - var err error - path, err = filepath.Abs(path) - if err != nil { - log.Error().Err(err).Str("path", path).Msg("convert absolute path failed") - return nil, err - } - file, err := os.Open(path) - if err != nil { - log.Error().Err(err).Str("path", path).Msg("open file failed") - return nil, err - } - defer file.Close() - - var line string - var lines []string - scanner := bufio.NewScanner(file) - // FIXME: resize scanner's capacity for lines over 64K - for scanner.Scan() { - text := strings.TrimSpace(scanner.Text()) - if text == "" || text == "\n" { - continue - } - if strings.HasSuffix(text, "\\") { - line = line + strings.Trim(text, "\\") - continue - } - line = line + text - lines = append(lines, line) - line = "" - } - return lines, scanner.Err() -} - func GetFileNameWithoutExtension(path string) string { base := filepath.Base(path) ext := filepath.Ext(base) diff --git a/hrp/pkg/convert/from_curl.go b/hrp/pkg/convert/from_curl.go index 7c60bf76..4ec2c81e 100644 --- a/hrp/pkg/convert/from_curl.go +++ b/hrp/pkg/convert/from_curl.go @@ -1,9 +1,11 @@ package convert import ( + "bufio" "fmt" "net/http" "net/url" + "os" "strings" "github.com/google/shlex" @@ -11,7 +13,6 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp" - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/json" ) @@ -94,12 +95,14 @@ func init() { // LoadCurlCase loads testcase from one or more curl commands in .txt file func LoadCurlCase(path string) (*hrp.TCase, error) { - cmds, err := builtin.ReadCmdLines(path) + cmds, err := readFileLines(path) if err != nil { return nil, err } tCase := &hrp.TCase{ - Config: &hrp.TConfig{Name: "testcase converted from curl command"}, + Config: &hrp.TConfig{ + Name: "testcase converted from curl command", + }, } for _, cmd := range cmds { tSteps, err := LoadCurlSteps(cmd) @@ -115,21 +118,24 @@ func LoadCurlCase(path string) (*hrp.TCase, error) { return tCase, nil } -// LoadSingleCurlCase one testcase from one curl command -func LoadSingleCurlCase(cmd string) (*hrp.TCase, error) { - tSteps, err := LoadCurlSteps(cmd) +func readFileLines(path string) ([]string, error) { + file, err := os.Open(path) if err != nil { + log.Error().Err(err).Str("path", path).Msg("open file failed") return nil, err } - tCase := &hrp.TCase{ - Config: &hrp.TConfig{Name: "testcase converted from curl command"}, - TestSteps: tSteps, + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || line == "\n" { + continue + } + lines = append(lines, line) } - err = tCase.MakeCompat() - if err != nil { - return nil, err - } - return tCase, nil + return lines, scanner.Err() } // LoadCurlSteps loads one teststep from one curl command From 0abd64abc25f47aec7a453b8f2662c78e17f8d90 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 23 Dec 2022 18:50:17 +0800 Subject: [PATCH 5/9] change: update README --- README.en.md | 12 ++++++++---- README.md | 27 ++++++++------------------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/README.en.md b/README.en.md index 7a6a14ab..5cd0c363 100644 --- a/README.en.md +++ b/README.en.md @@ -87,14 +87,20 @@ Usage: hrp [command] Available Commands: + adb simple utils for android device management boom run load test with boomer build build plugin for testing - completion generate the autocompletion script for the specified shell - convert convert to JSON/YAML/gotest/pytest testcases + completion Generate the autocompletion script for the specified shell + convert convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases + curl run integrated curl command + dns DNS resolution for different source and record types help Help about any command + ios simple utils for ios device management + ping run integrated ping command pytest run API test with pytest run run API test with go engine startproject create a scaffold project + traceroute run integrated traceroute command wiki visit https://httprunner.com Flags: @@ -120,8 +126,6 @@ Use "hrp [command] --help" for more information about a command. HttpRunner -如果你期望加入 HttpRunner 用户群,请看这里:[HttpRunner v4 用户交流群,它来啦!](https://httprunner.com/blog/join-chat-group) - [HttpRunner]: https://github.com/httprunner/httprunner [boomer]: https://github.com/myzhan/boomer [locust]: https://github.com/locustio/locust diff --git a/README.md b/README.md index 4debd21a..9a34261d 100644 --- a/README.md +++ b/README.md @@ -80,14 +80,20 @@ Usage: hrp [command] Available Commands: + adb simple utils for android device management boom run load test with boomer build build plugin for testing - completion generate the autocompletion script for the specified shell - convert convert to JSON/YAML/gotest/pytest testcases + completion Generate the autocompletion script for the specified shell + convert convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases + curl run integrated curl command + dns DNS resolution for different source and record types help Help about any command + ios simple utils for ios device management + ping run integrated ping command pytest run API test with pytest run run API test with go engine startproject create a scaffold project + traceroute run integrated traceroute command wiki visit https://httprunner.com Flags: @@ -107,21 +113,6 @@ Use "hrp [command] --help" for more information about a command. -## 赞助商 - -### 金牌赞助商 - -[霍格沃兹测试开发学社](https://ceshiren.com/) - -> [霍格沃兹测试开发学社](http://qrcode.testing-studio.com/f?from=httprunner&url=https://ceshiren.com)是业界领先的测试开发技术高端教育品牌,隶属于[测吧(北京)科技有限公司](http://qrcode.testing-studio.com/f?from=httprunner&url=https://www.testing-studio.com) 。学院课程由一线大厂测试经理与资深测试开发专家参与研发,实战驱动。课程涵盖 web/app 自动化测试、接口测试、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移&右移、精准测试、测试平台开发、测试管理等内容,帮助测试工程师实现测试开发技术转型。通过优秀的学社制度(奖学金、内推返学费、行业竞赛等多种方式)来实现学员、学社及用人企业的三方共赢。 - -> [进入测试开发技术能力测评!](http://qrcode.testing-studio.com/f?from=httprunner&url=https://ceshiren.com/t/topic/14940) - -### 开源服务赞助商 - -[Sentry](https://sentry.io/_/open-source/) - -HttpRunner is in Sentry Sponsored plan. ## Subscribe @@ -129,8 +120,6 @@ HttpRunner is in Sentry Sponsored plan. HttpRunner -如果你期望加入 HttpRunner 用户群,请看这里:[HttpRunner v4 用户交流群,它来啦!](https://httprunner.com/blog/join-chat-group) - [HttpRunner]: https://github.com/httprunner/httprunner [boomer]: https://github.com/myzhan/boomer [locust]: https://github.com/locustio/locust From 4734b35ac475867bd43f0babf4427b40f0da901f Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 23 Dec 2022 18:57:15 +0800 Subject: [PATCH 6/9] change: remove curl --- README.en.md | 1 - README.md | 1 - hrp/cmd/dial.go | 15 --------- hrp/internal/dial/curl.go | 70 --------------------------------------- 4 files changed, 87 deletions(-) delete mode 100644 hrp/internal/dial/curl.go diff --git a/README.en.md b/README.en.md index 5cd0c363..6c1115b1 100644 --- a/README.en.md +++ b/README.en.md @@ -92,7 +92,6 @@ Available Commands: build build plugin for testing completion Generate the autocompletion script for the specified shell convert convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases - curl run integrated curl command dns DNS resolution for different source and record types help Help about any command ios simple utils for ios device management diff --git a/README.md b/README.md index 9a34261d..d3dc435e 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,6 @@ Available Commands: build build plugin for testing completion Generate the autocompletion script for the specified shell convert convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases - curl run integrated curl command dns DNS resolution for different source and record types help Help about any command ios simple utils for ios device management diff --git a/hrp/cmd/dial.go b/hrp/cmd/dial.go index df14f3b1..cb602234 100644 --- a/hrp/cmd/dial.go +++ b/hrp/cmd/dial.go @@ -61,19 +61,6 @@ var traceRouteCmd = &cobra.Command{ }, } -var curlCmd = &cobra.Command{ - Use: "curl $url", - Short: "run integrated curl command", - Args: cobra.MinimumNArgs(1), - DisableFlagParsing: true, - PreRun: func(cmd *cobra.Command, args []string) { - setLogLevel(logLevel) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return dial.DoCurl(args) - }, -} - func init() { rootCmd.AddCommand(pingCmd) pingCmd.Flags().IntVarP(&pingOptions.Count, "count", "c", 10, "Stop after sending (and receiving) N packets") @@ -91,6 +78,4 @@ func init() { traceRouteCmd.Flags().IntVarP(&traceRouteOptions.MaxTTL, "max-hops", "m", 30, "Set the max number of hops (max TTL to be reached)") traceRouteCmd.Flags().IntVarP(&traceRouteOptions.Queries, "queries", "q", 1, "Set the number of probes per each hop") traceRouteCmd.Flags().BoolVar(&traceRouteOptions.SaveTests, "save-tests", false, "Save traceroute result as json") - - rootCmd.AddCommand(curlCmd) } diff --git a/hrp/internal/dial/curl.go b/hrp/internal/dial/curl.go deleted file mode 100644 index b40b29b3..00000000 --- a/hrp/internal/dial/curl.go +++ /dev/null @@ -1,70 +0,0 @@ -package dial - -import ( - "bytes" - "fmt" - "path/filepath" - - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" - "github.com/httprunner/httprunner/v4/hrp/internal/env" - "github.com/httprunner/httprunner/v4/hrp/internal/myexec" -) - -const ( - normalResult = "STDOUT" - errorResult = "STDERR" - failedResult = "FAILED" -) - -type CurlResult struct { - Result string `json:"result"` - ErrorMsg string `json:"errorMsg"` - ResultType string `json:"resultType"` -} - -func DoCurl(args []string) (err error) { - var saveTests bool - for i, arg := range args { - if arg == "--save-tests" { - args = append(args[:i], args[i+1:]...) - saveTests = true - } - } - var curlResult CurlResult - defer func() { - if saveTests { - curlResultName := fmt.Sprintf("curl_result_%v.json", env.StartTimeStr) - curlResultPath := filepath.Join(env.RootDir, curlResultName) - err = builtin.Dump2JSON(curlResult, curlResultPath) - if err != nil { - log.Error().Err(err).Msg("save dns resolution result failed") - } - } - }() - - cmd := myexec.Command("curl", args...) - var stdout, stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - err = cmd.Run() - if err != nil { - log.Error().Err(err).Msgf("fail to run curl command") - curlResult.ErrorMsg = err.Error() - curlResult.Result = stderr.String() - curlResult.ResultType = errorResult - return - } - if stdout.String() != "" { - fmt.Println(stdout.String()) - curlResult.Result = stdout.String() - curlResult.ResultType = normalResult - } else if stderr.String() != "" { - fmt.Println(stderr.String()) - curlResult.ErrorMsg = stderr.String() - curlResult.ResultType = errorResult - } - return -} From 287498f0983378ecf549aac90c670bcd1296b1ae Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 24 Dec 2022 12:40:32 +0800 Subject: [PATCH 7/9] change: remove traceroute --- README.en.md | 1 - README.md | 1 - hrp/cmd/dial.go | 26 +----- hrp/internal/dial/traceroute.go | 20 ----- hrp/internal/dial/traceroute_unix.go | 104 ------------------------ hrp/internal/dial/traceroute_windows.go | 103 ----------------------- 6 files changed, 2 insertions(+), 253 deletions(-) delete mode 100644 hrp/internal/dial/traceroute.go delete mode 100644 hrp/internal/dial/traceroute_unix.go delete mode 100644 hrp/internal/dial/traceroute_windows.go diff --git a/README.en.md b/README.en.md index 6c1115b1..0a30ce22 100644 --- a/README.en.md +++ b/README.en.md @@ -99,7 +99,6 @@ Available Commands: pytest run API test with pytest run run API test with go engine startproject create a scaffold project - traceroute run integrated traceroute command wiki visit https://httprunner.com Flags: diff --git a/README.md b/README.md index d3dc435e..2ca5dc81 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,6 @@ Available Commands: pytest run API test with pytest run run API test with go engine startproject create a scaffold project - traceroute run integrated traceroute command wiki visit https://httprunner.com Flags: diff --git a/hrp/cmd/dial.go b/hrp/cmd/dial.go index cb602234..9b6c9d77 100644 --- a/hrp/cmd/dial.go +++ b/hrp/cmd/dial.go @@ -1,7 +1,6 @@ package cmd import ( - "runtime" "time" "github.com/rs/zerolog/log" @@ -11,9 +10,8 @@ import ( ) var ( - pingOptions dial.PingOptions - dnsOptions dial.DnsOptions - traceRouteOptions dial.TraceRouteOptions + pingOptions dial.PingOptions + dnsOptions dial.DnsOptions ) var pingCmd = &cobra.Command{ @@ -46,21 +44,6 @@ var dnsCmd = &cobra.Command{ }, } -var traceRouteCmd = &cobra.Command{ - Use: "traceroute $url", - Short: "run integrated traceroute command", - Args: cobra.ExactArgs(1), - PreRun: func(cmd *cobra.Command, args []string) { - setLogLevel(logLevel) - }, - RunE: func(cmd *cobra.Command, args []string) error { - if runtime.GOOS == "windows" { - log.Info().Msg("using default probe number (3) on Windows") - } - return dial.DoTraceRoute(&traceRouteOptions, args) - }, -} - func init() { rootCmd.AddCommand(pingCmd) pingCmd.Flags().IntVarP(&pingOptions.Count, "count", "c", 10, "Stop after sending (and receiving) N packets") @@ -73,9 +56,4 @@ func init() { dnsCmd.Flags().IntVar(&dnsOptions.DnsRecordType, "dns-record", 1, "DNS record type\n1: A\n28: AAAA\n5: CNAME") dnsCmd.Flags().StringVar(&dnsOptions.DnsServer, "dns-server", "", "DNS server, only available for local DNS source") dnsCmd.Flags().BoolVar(&dnsOptions.SaveTests, "save-tests", false, "Save DNS resolution result as json") - - rootCmd.AddCommand(traceRouteCmd) - traceRouteCmd.Flags().IntVarP(&traceRouteOptions.MaxTTL, "max-hops", "m", 30, "Set the max number of hops (max TTL to be reached)") - traceRouteCmd.Flags().IntVarP(&traceRouteOptions.Queries, "queries", "q", 1, "Set the number of probes per each hop") - traceRouteCmd.Flags().BoolVar(&traceRouteOptions.SaveTests, "save-tests", false, "Save traceroute result as json") } diff --git a/hrp/internal/dial/traceroute.go b/hrp/internal/dial/traceroute.go deleted file mode 100644 index d20e5f1b..00000000 --- a/hrp/internal/dial/traceroute.go +++ /dev/null @@ -1,20 +0,0 @@ -package dial - -type TraceRouteOptions struct { - MaxTTL int - Queries int - SaveTests bool -} - -type TraceRouteResult struct { - IP string `json:"ip"` - Details []TraceRouteResultNode `json:"details"` - Suc bool `json:"suc"` - ErrMsg string `json:"errMsg"` -} - -type TraceRouteResultNode struct { - Id int `json:"id"` - Ip string `json:"ip"` - Time string `json:"time"` -} diff --git a/hrp/internal/dial/traceroute_unix.go b/hrp/internal/dial/traceroute_unix.go deleted file mode 100644 index 14532da9..00000000 --- a/hrp/internal/dial/traceroute_unix.go +++ /dev/null @@ -1,104 +0,0 @@ -//go:build darwin || linux - -package dial - -import ( - "bufio" - "fmt" - "net/url" - "path/filepath" - "regexp" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" - "github.com/httprunner/httprunner/v4/hrp/internal/env" - "github.com/httprunner/httprunner/v4/hrp/internal/myexec" -) - -var ( - regexIPAddr = regexp.MustCompile(`([\d.]+)`) - regexElapsedTime = regexp.MustCompile(`(\d+\.\d+)`) - regexTraceroutePass = regexp.MustCompile(fmt.Sprintf(`(\d+)[\s*]+(\S+)\s+\(%s\)\s+%s\s+ms`, regexIPAddr, regexElapsedTime)) - regexTracerouteFailure = regexp.MustCompile(`(\d+)[\s*]+$`) -) - -func DoTraceRoute(traceRouteOptions *TraceRouteOptions, args []string) (err error) { - if len(args) != 1 { - return errors.New("there should be one argument") - } - var traceRouteResult TraceRouteResult - defer func() { - if traceRouteOptions.SaveTests { - traceRouteResultName := fmt.Sprintf("traceroute_result_%v.json", env.StartTimeStr) - traceRouteResultPath := filepath.Join(env.RootDir, traceRouteResultName) - err = builtin.Dump2JSON(traceRouteResult, traceRouteResultPath) - if err != nil { - log.Error().Err(err).Msg("save traceroute result failed") - } - } - }() - - traceRouteTarget := args[0] - parsedURL, err := url.Parse(traceRouteTarget) - if err == nil && parsedURL.Host != "" { - log.Info().Msgf("parse input url %v and extract host %v", traceRouteTarget, parsedURL.Host) - traceRouteTarget = strings.Split(parsedURL.Host, ":")[0] - } - - cmd := myexec.Command("traceroute", "-m", strconv.Itoa(traceRouteOptions.MaxTTL), - "-q", strconv.Itoa(traceRouteOptions.Queries), traceRouteTarget) - stdout, _ := cmd.StdoutPipe() - - startT := time.Now() - defer func() { - log.Info().Msgf("for target %s, traceroute costs %v", traceRouteTarget, time.Since(startT)) - }() - - log.Info().Msgf("start to traceroute %v", traceRouteTarget) - err = cmd.Start() - if err != nil { - traceRouteResult.Suc = false - traceRouteResult.ErrMsg = "execute traceroute failed" - log.Error().Err(err).Msg("start command failed") - return - } - - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - hopLine := scanner.Text() - fmt.Println(hopLine) - failureLine := regexTracerouteFailure.FindStringSubmatch(hopLine) - if len(failureLine) == 2 { - hopID, _ := strconv.Atoi(failureLine[1]) - traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{ - Id: hopID, - }) - continue - } - passLine := regexTraceroutePass.FindStringSubmatch(hopLine) - if len(passLine) == 5 { - hopID, _ := strconv.Atoi(passLine[1]) - traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{ - Id: hopID, - Ip: passLine[3], - Time: passLine[4], - }) - traceRouteResult.Suc = true - } - } - hopCount := len(traceRouteResult.Details) - traceRouteResult.IP = traceRouteResult.Details[hopCount-1].Ip - err = cmd.Wait() - if err != nil { - traceRouteResult.Suc = false - traceRouteResult.ErrMsg = "wait traceroute finish failed" - log.Error().Err(err).Msg("wait command failed") - return - } - return -} diff --git a/hrp/internal/dial/traceroute_windows.go b/hrp/internal/dial/traceroute_windows.go deleted file mode 100644 index 92f5ea28..00000000 --- a/hrp/internal/dial/traceroute_windows.go +++ /dev/null @@ -1,103 +0,0 @@ -//go:build windows - -package dial - -import ( - "bufio" - "fmt" - "net/url" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" - "github.com/httprunner/httprunner/v4/hrp/internal/myexec" -) - -var ( - regexTracertPass = regexp.MustCompile(`(\d+)[\s*<]+(\d+)\s+ms`) - regexTracertFailure = regexp.MustCompile(`(\d+)[\s*]+Request timed out`) -) - -func DoTraceRoute(traceRouteOptions *TraceRouteOptions, args []string) (err error) { - if len(args) != 1 { - return errors.New("there should be one argument") - } - var traceRouteResult TraceRouteResult - defer func() { - if traceRouteOptions.SaveTests { - traceRouteResultName := fmt.Sprintf("traceroute_result_%v.json", env.StartTimeStr) - traceRouteResultPath := filepath.Join(env.RootDir, traceRouteResultName) - err = builtin.Dump2JSON(traceRouteResult, traceRouteResultPath) - if err != nil { - log.Error().Err(err).Msg("save traceroute result failed") - } - } - }() - - traceRouteTarget := args[0] - parsedURL, err := url.Parse(traceRouteTarget) - if err == nil && parsedURL.Host != "" { - log.Info().Msgf("parse input url %v and extract host %v", traceRouteTarget, parsedURL.Host) - traceRouteTarget = strings.Split(parsedURL.Host, ":")[0] - } - - cmd := myexec.Command("tracert", "-h", strconv.Itoa(traceRouteOptions.MaxTTL), traceRouteTarget) - stdout, _ := cmd.StdoutPipe() - - startT := time.Now() - defer func() { - log.Info().Msgf("for target %s, traceroute costs %v", traceRouteTarget, time.Since(startT)) - }() - - log.Info().Msgf("start to traceroute %v", traceRouteTarget) - err = cmd.Start() - if err != nil { - traceRouteResult.Suc = false - traceRouteResult.ErrMsg = "execute traceroute failed" - log.Error().Err(err).Msg("start command failed") - return - } - - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - hopLine := scanner.Text() - fmt.Println(hopLine) - failureLine := regexTracertFailure.FindStringSubmatch(hopLine) - if len(failureLine) == 2 { - hopID, _ := strconv.Atoi(failureLine[1]) - traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{ - Id: hopID, - }) - continue - } - passLine := regexTracertPass.FindStringSubmatch(hopLine) - if len(passLine) == 3 { - hopID, _ := strconv.Atoi(passLine[1]) - fields := strings.Fields(hopLine) - hopIP := strings.Trim(fields[len(fields)-1], "[]") - traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{ - Id: hopID, - Ip: hopIP, - Time: passLine[2], - }) - traceRouteResult.Suc = true - } - } - hopCount := len(traceRouteResult.Details) - traceRouteResult.IP = traceRouteResult.Details[hopCount-1].Ip - err = cmd.Wait() - if err != nil { - traceRouteResult.Suc = false - traceRouteResult.ErrMsg = "wait traceroute finish failed" - log.Error().Err(err).Msg("wait command failed") - return - } - return -} From 53fbfd3ef0e13e8c22fa20e07c6bf89a4af68ec9 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 25 Dec 2022 19:05:14 +0800 Subject: [PATCH 8/9] change: remove unused UI APIs --- examples/uitest/demo_android_douyin_test.go | 9 +- examples/uitest/wda_log_data.json | 13 +- examples/uitest/wda_log_test.go | 1 + hrp/pkg/uixt/android_action.go | 158 ---- hrp/pkg/uixt/android_device_test.go | 18 - hrp/pkg/uixt/android_driver.go | 345 +------- hrp/pkg/uixt/android_elment.go | 306 ------- hrp/pkg/uixt/android_test.go | 915 +------------------- hrp/pkg/uixt/ext.go | 144 ++- hrp/pkg/uixt/gesture.go | 44 - hrp/pkg/uixt/gesture_test.go | 25 - hrp/pkg/uixt/interface.go | 526 +---------- hrp/pkg/uixt/ios_action.go | 373 -------- hrp/pkg/uixt/ios_device.go | 173 ---- hrp/pkg/uixt/ios_driver.go | 266 ++---- hrp/pkg/uixt/ios_element.go | 478 ---------- hrp/pkg/uixt/ios_test.go | 757 ---------------- hrp/pkg/uixt/tap.go | 33 - hrp/pkg/uixt/tap_test.go | 19 +- hrp/pkg/uixt/touch.go | 33 - hrp/pkg/uixt/touch_test.go | 39 - hrp/runner.go | 2 +- hrp/step_mobile_ui.go | 8 - hrp/step_mobile_ui_test.go | 2 +- 24 files changed, 190 insertions(+), 4497 deletions(-) delete mode 100644 hrp/pkg/uixt/android_action.go delete mode 100644 hrp/pkg/uixt/android_device_test.go delete mode 100644 hrp/pkg/uixt/android_elment.go delete mode 100644 hrp/pkg/uixt/gesture.go delete mode 100644 hrp/pkg/uixt/gesture_test.go delete mode 100644 hrp/pkg/uixt/ios_action.go delete mode 100644 hrp/pkg/uixt/ios_element.go delete mode 100644 hrp/pkg/uixt/touch.go delete mode 100644 hrp/pkg/uixt/touch_test.go diff --git a/examples/uitest/demo_android_douyin_test.go b/examples/uitest/demo_android_douyin_test.go index bbfa6391..8ef5d2c4 100644 --- a/examples/uitest/demo_android_douyin_test.go +++ b/examples/uitest/demo_android_douyin_test.go @@ -12,7 +12,7 @@ import ( func TestAndroidDouYinLive(t *testing.T) { testCase := &hrp.TestCase{ Config: hrp.NewConfig("通过 feed 头像进入抖音直播间"). - SetAndroid(uixt.WithAdbLogOn(true), uixt.WithSerialNumber("2d06bf70")), + SetAndroid(uixt.WithAdbLogOn(true)), TestSteps: []hrp.IStep{ hrp.NewStep("启动抖音"). Android(). @@ -36,13 +36,6 @@ func TestAndroidDouYinLive(t *testing.T) { }, } - if err := testCase.Dump2JSON("demo_android_douyin_live.json"); err != nil { - t.Fatal(err) - } - if err := testCase.Dump2YAML("demo_android_douyin_live.yaml"); err != nil { - t.Fatal(err) - } - runner := hrp.NewRunner(t).SetSaveTests(true) err := runner.Run(testCase) if err != nil { diff --git a/examples/uitest/wda_log_data.json b/examples/uitest/wda_log_data.json index 0237e46a..b89e4fbe 100644 --- a/examples/uitest/wda_log_data.json +++ b/examples/uitest/wda_log_data.json @@ -6,9 +6,16 @@ }, "ios": [ { + "perf_options": { + "sys_cpu": true, + "sys_mem": true, + "fps": true, + "network": true + }, "port": 8700, "mjpeg_port": 8800, - "log_on": true + "log_on": true, + "xctest_bundle_id": "com.gtf.wda.runner.xctrunner" } ] }, @@ -63,8 +70,8 @@ "actions": [ { "method": "tap_ocr", - "params": "购物", - "identifier": "点击购物" + "params": "商城", + "identifier": "点击商城" }, { "method": "sleep", diff --git a/examples/uitest/wda_log_test.go b/examples/uitest/wda_log_test.go index 3c03a458..39943f09 100644 --- a/examples/uitest/wda_log_test.go +++ b/examples/uitest/wda_log_test.go @@ -24,6 +24,7 @@ func TestWDALog(t *testing.T) { uixt.WithIOSPerfNetwork(true), uixt.WithIOSPerfFPS(true), ), + uixt.WithXCTest("com.gtf.wda.runner.xctrunner"), ), TestSteps: []hrp.IStep{ hrp.NewStep("启动抖音"). diff --git a/hrp/pkg/uixt/android_action.go b/hrp/pkg/uixt/android_action.go deleted file mode 100644 index b8081614..00000000 --- a/hrp/pkg/uixt/android_action.go +++ /dev/null @@ -1,158 +0,0 @@ -package uixt - -import "strings" - -type touchGesture struct { - Touch PointF `json:"touch"` - Time float64 `json:"time"` -} - -type TouchAction []touchGesture - -func NewTouchAction(cap ...int) *TouchAction { - if len(cap) == 0 || cap[0] <= 0 { - cap = []int{8} - } - tmp := make(TouchAction, 0, cap[0]) - return &tmp -} - -func (ta *TouchAction) Add(x, y int, startTime ...float64) *TouchAction { - return ta.AddFloat(float64(x), float64(y), startTime...) -} - -func (ta *TouchAction) AddFloat(x, y float64, startTime ...float64) *TouchAction { - if len(startTime) == 0 { - var tmp float64 = 0 - if len(*ta) != 0 { - g := (*ta)[len(*ta)-1] - tmp = g.Time + 0.05 - } - startTime = []float64{tmp} - } - *ta = append(*ta, touchGesture{Touch: PointF{x, y}, Time: startTime[0]}) - return ta -} - -func (ta *TouchAction) AddPoint(point Point, startTime ...float64) *TouchAction { - return ta.AddFloat(float64(point.X), float64(point.Y), startTime...) -} - -func (ta *TouchAction) AddPointF(point PointF, startTime ...float64) *TouchAction { - return ta.AddFloat(point.X, point.Y, startTime...) -} - -func (ud *uiaDriver) MultiPointerGesture(gesture1 *TouchAction, gesture2 *TouchAction, tas ...*TouchAction) (err error) { - // Must provide coordinates for at least 2 pointers - actions := make([]*TouchAction, 0) - actions = append(actions, gesture1, gesture2) - if len(tas) != 0 { - actions = append(actions, tas...) - } - data := map[string]interface{}{ - "actions": actions, - } - // register(postHandler, new MultiPointerGesture("/wd/hub/session/:sessionId/touch/multi/perform")) - _, err = ud.httpPOST(data, "/session", ud.sessionId, "/touch/multi/perform") - return -} - -type w3cGesture map[string]interface{} - -func _newW3CGesture() w3cGesture { - return make(w3cGesture) -} - -func (g w3cGesture) _set(key string, value interface{}) w3cGesture { - g[key] = value - return g -} - -func (g w3cGesture) pause(duration float64) w3cGesture { - return g._set("type", "pause"). - _set("duration", duration) -} - -func (g w3cGesture) keyDown(value string) w3cGesture { - return g._set("type", "keyDown"). - _set("value", value) -} - -func (g w3cGesture) keyUp(value string) w3cGesture { - return g._set("type", "keyUp"). - _set("value", value) -} - -func (g w3cGesture) pointerDown(button int) w3cGesture { - return g._set("type", "pointerDown")._set("button", button) -} - -func (g w3cGesture) pointerUp(button int) w3cGesture { - return g._set("type", "pointerUp")._set("button", button) -} - -func (g w3cGesture) pointerMove(x, y float64, origin string, duration float64, pressureAndSize ...float64) w3cGesture { - switch len(pressureAndSize) { - case 1: - g._set("pressure", pressureAndSize[0]) - case 2: - g._set("pressure", pressureAndSize[0]) - g._set("size", pressureAndSize[1]) - } - return g._set("type", "pointerMove"). - _set("duration", duration). - _set("origin", origin). - _set("x", x). - _set("y", y) -} - -func (g w3cGesture) size(size ...float64) w3cGesture { - if len(size) == 0 { - size = []float64{1.0} - } - return g._set("size", size[0]) -} - -func (g w3cGesture) pressure(pressure ...float64) w3cGesture { - if len(pressure) == 0 { - pressure = []float64{1.0} - } - return g._set("pressure", pressure[0]) -} - -type W3CGestures []w3cGesture - -func NewW3CGestures(cap ...int) *W3CGestures { - if len(cap) == 0 || cap[0] <= 0 { - cap = []int{8} - } - tmp := make(W3CGestures, 0, cap[0]) - return &tmp -} - -func (g *W3CGestures) Pause(duration ...float64) *W3CGestures { - if len(duration) == 0 || duration[0] < 0 { - duration = []float64{0.5} - } - *g = append(*g, _newW3CGesture().pause(duration[0]*1000)) - return g -} - -func (g *W3CGestures) KeyDown(value string) *W3CGestures { - *g = append(*g, _newW3CGesture().keyDown(value)) - return g -} - -func (g *W3CGestures) KeyUp(value string) *W3CGestures { - *g = append(*g, _newW3CGesture().keyUp(value)) - return g -} - -func (g *W3CGestures) SendKeys(text string) *W3CGestures { - ss := strings.Split(text, "") - for i := range ss { - g.KeyDown(ss[i]) - g.KeyUp(ss[i]) - } - return g -} diff --git a/hrp/pkg/uixt/android_device_test.go b/hrp/pkg/uixt/android_device_test.go deleted file mode 100644 index 2167d2f5..00000000 --- a/hrp/pkg/uixt/android_device_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package uixt - -import ( - "fmt" - "testing" - - "github.com/httprunner/httprunner/v4/hrp/internal/json" -) - -func TestConvertPoints(t *testing.T) { - data := "10-09 20:16:48.216 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317808206,\"ext\":\"输入\",\"from\":{\"x\":0.0,\"y\":0.0},\"operation\":\"Gtf-SendKeys\",\"run_time\":627,\"start\":1665317807579,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":0.0,\"y\":0.0}}\n10-09 20:18:22.899 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317902898,\"ext\":\"进入直播间\",\"from\":{\"x\":717.0,\"y\":2117.5},\"operation\":\"Gtf-Tap\",\"run_time\":121,\"start\":1665317902777,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":717.0,\"y\":2117.5}}\n10-09 20:18:32.063 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317912062,\"ext\":\"第一次上划\",\"from\":{\"x\":1437.0,\"y\":2409.9},\"operation\":\"Gtf-Swipe\",\"run_time\":32,\"start\":1665317912030,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":1437.0,\"y\":2409.9}}" - eps := ConvertPoints(data) - if len(eps) != 3 { - t.Fatal() - } - jsons, _ := json.Marshal(eps) - println(fmt.Sprintf("%v", string(jsons))) -} diff --git a/hrp/pkg/uixt/android_driver.go b/hrp/pkg/uixt/android_driver.go index 31d43c07..45c7e519 100644 --- a/hrp/pkg/uixt/android_driver.go +++ b/hrp/pkg/uixt/android_driver.go @@ -309,45 +309,12 @@ func (ud *uiaDriver) StopCamera() (err error) { return } -func (ud *uiaDriver) ActiveAppInfo() (info AppInfo, err error) { - // TODO - return info, errDriverNotImplemented -} - -func (ud *uiaDriver) ActiveAppsList() (appsList []AppBaseInfo, err error) { - // TODO - return appsList, errDriverNotImplemented -} - -func (ud *uiaDriver) AppState(bundleId string) (runState AppState, err error) { - // TODO - return runState, errDriverNotImplemented -} - -func (ud *uiaDriver) IsLocked() (locked bool, err error) { - // TODO - return locked, errDriverNotImplemented -} - -func (ud *uiaDriver) Unlock() (err error) { - // TODO - return errDriverNotImplemented -} - -func (ud *uiaDriver) Lock() (err error) { - // TODO - return errDriverNotImplemented -} - func (ud *uiaDriver) Homescreen() (err error) { return ud.PressKeyCode(KCHome, KMEmpty) } func (ud *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) { - if len(flags) == 0 { - flags = []KeyFlag{KFFromSystem} - } - return ud._pressKeyCode(keyCode, metaState, KFFromSystem) + return ud._pressKeyCode(keyCode, metaState, flags...) } func (ud *uiaDriver) _pressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) { @@ -365,138 +332,28 @@ func (ud *uiaDriver) _pressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ... return } -func (ud *uiaDriver) AlertText() (text string, err error) { - // register(getHandler, new GetAlertText("/wd/hub/session/:sessionId/alert/text")) - var rawResp rawResponse - if rawResp, err = ud.httpGET("/session", ud.sessionId, "alert/text"); err != nil { - return "", err - } - reply := new(struct{ Value string }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return "", err - } - - text = reply.Value - return -} - -func (ud *uiaDriver) AlertButtons() (btnLabels []string, err error) { - // TODO - return btnLabels, errDriverNotImplemented -} - -func (ud *uiaDriver) AlertAccept(label ...string) (err error) { - data := map[string]interface{}{ - "buttonLabel": nil, - } - if len(label) != 0 { - data["buttonLabel"] = label[0] - } - // register(postHandler, new AcceptAlert("/wd/hub/session/:sessionId/alert/accept")) - _, err = ud.httpPOST(data, "/session", ud.sessionId, "alert/accept") - return -} - -func (ud *uiaDriver) AlertDismiss(label ...string) (err error) { - data := map[string]interface{}{ - "buttonLabel": nil, - } - if len(label) != 0 { - data["buttonLabel"] = label[0] - } - // register(postHandler, new DismissAlert("/wd/hub/session/:sessionId/alert/dismiss")) - _, err = ud.httpPOST(data, "/session", ud.sessionId, "alert/dismiss") - return -} - -func (ud *uiaDriver) AlertSendKeys(text string) (err error) { - // TODO - return errDriverNotImplemented -} - -func (ud *uiaDriver) check() error { - if ud.adbDevice.Serial() == "" { - return errors.New("adb daemon: the device is not ready") - } - return nil -} - -func (ud *uiaDriver) AppLaunch(bundleId string, launchOpt ...AppLaunchOption) (err error) { - if err = ud.check(); err != nil { - return err - } - - var sOutput string - if sOutput, err = ud.adbDevice.RunShellCommand("monkey -p", bundleId, "-c android.intent.category.LAUNCHER 1"); err != nil { +func (ud *uiaDriver) AppLaunch(bundleId string) (err error) { + // 不指定 Activity 名称启动(启动主 Activity) + // adb shell monkey -p -c android.intent.category.LAUNCHER 1 + sOutput, err := ud.adbDevice.RunShellCommand( + "monkey", "-p", bundleId, "-c", "android.intent.category.LAUNCHER", "1", + ) + if err != nil { return err } if strings.Contains(sOutput, "monkey aborted") { return fmt.Errorf("app launch: %s", strings.TrimSpace(sOutput)) } - - if len(launchOpt) != 0 { - var ce error - exists := func(ud WebDriver) (bool, error) { - for _, opt := range launchOpt { - if bySelector, ok := opt["bySelector"]; ok { - for _, e := range bySelector.([]BySelector) { - _, ce = ud.FindElement(e) - if ce == nil { - return true, nil - } - } - } - } - return false, nil - } - if err = ud.WaitWithTimeoutAndInterval(exists, 45, 1); err != nil { - return fmt.Errorf("app launch: %s: %w", err.Error(), ce) - } - } - return -} - -func (ud *uiaDriver) AppLaunchUnattached(bundleId string) (err error) { - // TODO - return errDriverNotImplemented -} - -// Dispose corresponds to the command: -// adb -s $serial forward --remove $localPort -func (ud *uiaDriver) Dispose() (err error) { - if err = ud.check(); err != nil { - return err - } - if ud.localPort == 0 { - return nil - } - return ud.adbDevice.ForwardKill(ud.localPort) + return nil } func (ud *uiaDriver) AppTerminate(bundleId string) (successful bool, err error) { - if err = ud.check(); err != nil { - return false, err - } - + // 强制停止应用,停止 相关的进程 + // adb shell am force-stop _, err = ud.adbDevice.RunShellCommand("am", "force-stop", bundleId) return err == nil, err } -func (ud *uiaDriver) AppActivate(bundleId string) (err error) { - // TODO - return errDriverNotImplemented -} - -func (ud *uiaDriver) AppDeactivate(second float64) (err error) { - // TODO - return errDriverNotImplemented -} - -func (ud *uiaDriver) AppAuthReset(resource ProtectedResource) (err error) { - // TODO - return errDriverNotImplemented -} - func (ud *uiaDriver) Tap(x, y int, options ...DataOption) error { return ud.TapFloat(float64(x), float64(y), options...) } @@ -608,20 +465,6 @@ func (ud *uiaDriver) ForceTouchFloat(x, y, pressure float64, second ...float64) return errDriverNotImplemented } -func (ud *uiaDriver) PerformW3CActions(actions *W3CActions) (err error) { - data := map[string]interface{}{ - "actions": actions, - } - // register(postHandler, new W3CActions("/wd/hub/session/:sessionId/actions")) - _, err = ud.httpPOST(data, "/session", ud.sessionId, "/actions") - return -} - -func (ud *uiaDriver) PerformAppiumTouchActions(touchActs *TouchActions) (err error) { - // TODO - return errDriverNotImplemented -} - func (ud *uiaDriver) SetPasteboard(contentType PasteboardType, content string) (err error) { lbl := content @@ -679,31 +522,7 @@ func (ud *uiaDriver) SendKeys(text string, options ...DataOption) (err error) { } func (ud *uiaDriver) Input(text string, options ...DataOption) (err error) { - data := map[string]interface{}{ - "view": text, - } - // new data options in post data for extra uiautomator configurations - newData := NewData(data, options...) - - var element WebElement - if valuetext, ok := newData["textview"]; ok { - element, err = ud.FindElement(BySelector{UiAutomator: NewUiSelectorHelper().TextContains(fmt.Sprintf("%v", valuetext)).String()}) - } else if valueid, ok := newData["id"]; ok { - element, err = ud.FindElement(BySelector{ResourceIdID: fmt.Sprintf("%v", valueid)}) - } else if valuedesc, ok := newData["description"]; ok { - element, err = ud.FindElement(BySelector{UiAutomator: NewUiSelectorHelper().Description(fmt.Sprintf("%v", valuedesc)).String()}) - } else { - element, err = ud.FindElement(BySelector{ClassName: ElementType{EditText: true}}) - } - if err != nil { - return err - } - return element.SendKeys(text, options...) -} - -func (ud *uiaDriver) KeyboardDismiss(keyNames ...string) (err error) { - // TODO - return errDriverNotImplemented + return ud.SendKeys(text, options...) } func (ud *uiaDriver) PressButton(devBtn DeviceButton) (err error) { @@ -711,47 +530,6 @@ func (ud *uiaDriver) PressButton(devBtn DeviceButton) (err error) { return errDriverNotImplemented } -func (ud *uiaDriver) IOHIDEvent(pageID EventPageID, usageID EventUsageID, duration ...float64) (err error) { - // TODO - return errDriverNotImplemented -} - -func (ud *uiaDriver) ExpectNotification(notifyName string, notifyType NotificationType, second ...int) (err error) { - // register(postHandler, new OpenNotification("/wd/hub/session/:sessionId/appium/device/open_notifications")) - _, err = ud.httpPOST(nil, "/session", ud.sessionId, "appium/device/open_notifications") - return -} - -func (ud *uiaDriver) SiriActivate(text string) (err error) { - // TODO - return errDriverNotImplemented -} - -func (ud *uiaDriver) SiriOpenUrl(url string) (err error) { - // TODO - return errDriverNotImplemented -} - -func (ud *uiaDriver) Orientation() (orientation Orientation, err error) { - // register(getHandler, new GetOrientation("/wd/hub/session/:sessionId/orientation")) - var rawResp rawResponse - if rawResp, err = ud.httpGET("/session", ud.sessionId, "orientation"); err != nil { - return "", err - } - reply := new(struct{ Value Orientation }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return "", err - } - - orientation = reply.Value - return -} - -func (ud *uiaDriver) SetOrientation(orientation Orientation) (err error) { - // TODO - return errDriverNotImplemented -} - func (ud *uiaDriver) Rotation() (rotation Rotation, err error) { // register(getHandler, new GetRotation("/wd/hub/session/:sessionId/rotation")) var rawResp rawResponse @@ -772,105 +550,6 @@ func (ud *uiaDriver) SetRotation(rotation Rotation) (err error) { return errDriverNotImplemented } -func (ud *uiaDriver) MatchTouchID(isMatch bool) (err error) { - // TODO - return errDriverNotImplemented -} - -func (ud *uiaDriver) _findElements(method, selector string, elementID ...string) (elements []WebElement, err error) { - // register(postHandler, new FindElements("/wd/hub/session/:sessionId/elements")) - data := map[string]interface{}{ - "strategy": method, - "selector": selector, - } - if len(elementID) != 0 { - data["context"] = elementID[0] - } - var rawResp rawResponse - if rawResp, err = ud.httpPOST(data, "/session", ud.sessionId, "/elements"); err != nil { - return nil, err - } - reply := new(struct{ Value []map[string]string }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return nil, err - } - if len(reply.Value) == 0 { - return nil, fmt.Errorf("no such element: unable to find an element using '%s', value '%s'", method, selector) - } - elements = make([]WebElement, len(reply.Value)) - for i, elem := range reply.Value { - var id string - if id = elementIDFromValue(elem); id == "" { - return nil, fmt.Errorf("invalid element returned: %+v", reply) - } - uie := WebElement(uiaElement{parent: ud, id: id}) - elements[i] = uie - } - return -} - -func (ud *uiaDriver) _findElement(method, selector string, elementID ...string) (elem *uiaElement, err error) { - // register(postHandler, new FindElement("/wd/hub/session/:sessionId/element")) - data := map[string]interface{}{ - "strategy": method, - "selector": selector, - } - if len(elementID) != 0 { - data["context"] = elementID[0] - } - var rawResp rawResponse - if rawResp, err = ud.httpPOST(data, "/session", ud.sessionId, "/element"); err != nil { - return nil, err - } - reply := new(struct{ Value map[string]string }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return nil, err - } - if len(reply.Value) == 0 { - return nil, fmt.Errorf("no such element: unable to find an element using '%s', value '%s'", method, selector) - } - var id string - if id = elementIDFromValue(reply.Value); id == "" { - return nil, fmt.Errorf("invalid element returned: %+v", reply) - } - elem = &uiaElement{parent: ud, id: id} - return -} - -func (ud *uiaDriver) ActiveElement() (element WebElement, err error) { - // TODO - return element, errDriverNotImplemented -} - -func (ud *uiaDriver) FindElement(by BySelector) (element WebElement, err error) { - return ud._findElement(by.getUsingAndValue()) -} - -func (ud *uiaDriver) FindElements(by BySelector) (elements []WebElement, err error) { - // [[FBRoute POST:@"/elements"] respondWithTarget:self action:@selector(handleFindElements:)] - using, value := by.getUsingAndValue() - data := map[string]interface{}{ - "using": using, - "value": value, - } - var rawResp rawResponse - if rawResp, err = ud.httpPOST(data, "/session", ud.sessionId, "/elements"); err != nil { - return nil, err - } - var elementIDs []string - if elementIDs, err = rawResp.valueConvertToElementIDs(); err != nil { - if errors.Is(err, errNoSuchElement) { - return nil, fmt.Errorf("%w: unable to find an element using '%s', value '%s'", err, using, value) - } - return nil, err - } - elements = make([]WebElement, len(elementIDs)) - for i := range elementIDs { - elements[i] = WebElement(uiaElement{parent: ud, id: elementIDs[i]}) - } - return -} - func (ud *uiaDriver) Screenshot() (raw *bytes.Buffer, err error) { // register(getHandler, new CaptureScreenshot("/wd/hub/session/:sessionId/screenshot")) var rawResp rawResponse diff --git a/hrp/pkg/uixt/android_elment.go b/hrp/pkg/uixt/android_elment.go deleted file mode 100644 index 760f52ab..00000000 --- a/hrp/pkg/uixt/android_elment.go +++ /dev/null @@ -1,306 +0,0 @@ -package uixt - -import ( - "bytes" - "encoding/base64" - "encoding/json" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" -) - -var errElementNotImplemented = errors.New("element method not implemented") - -type uiaElement struct { - parent *uiaDriver - id string -} - -func (ue uiaElement) Click() (err error) { - // register(postHandler, new Click("/wd/hub/session/:sessionId/element/:id/click")) - _, err = ue.parent.httpPOST(nil, "/session", ue.parent.sessionId, "/element", ue.id, "/click") - return -} - -func (ue uiaElement) SendKeys(text string, options ...DataOption) (err error) { - // register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/element/:id/value")) - // https://github.com/appium/appium-uiutomator2-server/blob/master/app/src/main/java/io/appium/uiutomator2/handler/SendKeysToElement.java#L76-L85 - data := map[string]interface{}{ - "text": text, - } - - // new data options in post data for extra uiautomator configurations - newData := NewData(data, options...) - - _, err = ue.parent.httpPOST(newData, "/session", ue.parent.sessionId, "/element", ue.id, "/value") - return -} - -func (ue uiaElement) Clear() (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) Tap(x, y int) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) TapFloat(x, y float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) DoubleTap() (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) TouchAndHold(second ...float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) TwoFingerTap() (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) TapWithNumberOfTaps(numberOfTaps, numberOfTouches int) (err error) { - // Todo: implement - log.Fatal().Msg("not support") - return -} - -func (ue uiaElement) ForceTouch(pressure float64, second ...float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) ForceTouchFloat(x, y, pressure float64, second ...float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) Drag(fromX, fromY, toX, toY int, steps ...float64) (err error) { - return ue.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), steps...) -} - -func (ue uiaElement) DragFloat(fromX, fromY, toX, toY float64, steps ...float64) (err error) { - if len(steps) == 0 { - steps = []float64{12 * 10} - } else { - steps[0] = 12 * 10 - } - data := map[string]interface{}{ - "elementId": ue.id, - "endX": toX, - "endY": toY, - "steps": steps[0], - } - return ue.parent._drag(data) -} - -func (ue uiaElement) Swipe(fromX, fromY, toX, toY int) error { - return ue.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY)) -} - -func (ue uiaElement) SwipeFloat(fromX, fromY, toX, toY float64) error { - options := []DataOption{ - WithDataSteps(12), - WithCustomOption("elementId", ue.id), - } - return ue.parent._swipe(fromX, fromY, toX, toY, options...) -} - -func (ue uiaElement) SwipeDirection(direction Direction, velocity ...float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) Pinch(scale, velocity float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) PinchToZoomOutByW3CAction(scale ...float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) Rotate(rotation float64, velocity ...float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) PickerWheelSelect(order PickerWheelOrder, offset ...int) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) scroll(data interface{}) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) ScrollElementByName(name string) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) ScrollElementByPredicate(predicate string) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) ScrollToVisible() (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) ScrollDirection(direction Direction, distance ...float64) (err error) { - // TODO - return errElementNotImplemented -} - -func (ue uiaElement) FindElement(by BySelector) (element WebElement, err error) { - method, selector := by.getMethodAndSelector() - return ue.parent._findElement(method, selector, ue.id) -} - -func (ue uiaElement) FindElements(by BySelector) (elements []WebElement, err error) { - method, selector := by.getMethodAndSelector() - return ue.parent._findElements(method, selector, ue.id) -} - -func (ue uiaElement) FindVisibleCells() (elements []WebElement, err error) { - // TODO - return elements, errElementNotImplemented -} - -func (ue uiaElement) Rect() (rect Rect, err error) { - // register(getHandler, new GetRect("/wd/hub/session/:sessionId/element/:id/rect")) - var rawResp rawResponse - if rawResp, err = ue.parent.httpGET("/session", ue.parent.sessionId, "/element", ue.id, "/rect"); err != nil { - return Rect{}, err - } - reply := new(struct{ Value Rect }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return Rect{}, err - } - rect = reply.Value - return -} - -func (ue uiaElement) Location() (point Point, err error) { - // register(getHandler, new Location("/wd/hub/session/:sessionId/element/:id/location")) - var rawResp rawResponse - if rawResp, err = ue.parent.httpGET("/session", ue.parent.sessionId, "/element", ue.id, "/location"); err != nil { - return Point{-1, -1}, err - } - reply := new(struct{ Value Point }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return Point{-1, -1}, err - } - point = reply.Value - return -} - -func (ue uiaElement) Size() (size Size, err error) { - // register(getHandler, new GetSize("/wd/hub/session/:sessionId/element/:id/size")) - var rawResp rawResponse - if rawResp, err = ue.parent.httpGET("/session", ue.parent.sessionId, "/element", ue.id, "/size"); err != nil { - return Size{-1, -1}, err - } - reply := new(struct{ Value Size }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return Size{-1, -1}, err - } - size = reply.Value - return -} - -func (ue uiaElement) Text() (text string, err error) { - // register(getHandler, new GetText("/wd/hub/session/:sessionId/element/:id/text")) - var rawResp rawResponse - if rawResp, err = ue.parent.httpGET("/session", ue.parent.sessionId, "/element", ue.id, "/text"); err != nil { - return "", err - } - reply := new(struct{ Value string }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return "", err - } - text = reply.Value - return -} - -func (ue uiaElement) Type() (elemType string, err error) { - // TODO - return elemType, errElementNotImplemented -} - -func (ue uiaElement) IsEnabled() (enabled bool, err error) { - // TODO - return enabled, errElementNotImplemented -} - -func (ue uiaElement) IsDisplayed() (displayed bool, err error) { - // TODO - return displayed, errElementNotImplemented -} - -func (ue uiaElement) IsSelected() (selected bool, err error) { - // TODO - return selected, errElementNotImplemented -} - -func (ue uiaElement) IsAccessible() (accessible bool, err error) { - // TODO - return accessible, errElementNotImplemented -} - -func (ue uiaElement) IsAccessibilityContainer() (isAccessibilityContainer bool, err error) { - // TODO - return isAccessibilityContainer, errElementNotImplemented -} - -func (ue uiaElement) GetAttribute(attr ElementAttribute) (value string, err error) { - // register(getHandler, new GetElementAttribute("/wd/hub/session/:sessionId/element/:id/attribute/:name")) - var rawResp rawResponse - if rawResp, err = ue.parent.httpGET("/session", ue.parent.sessionId, "/element", ue.id, "/attribute", attr.getAttributeName()); err != nil { - return "", err - } - reply := new(struct{ Value string }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return "", err - } - value = reply.Value - return -} - -func (ue uiaElement) UID() (uid string) { - return ue.id -} - -func (ue uiaElement) Screenshot() (raw *bytes.Buffer, err error) { - // W3C endpoint - // register(getHandler, new GetElementScreenshot("/wd/hub/session/:sessionId/element/:id/screenshot")) - // JSONWP endpoint - // register(getHandler, new GetElementScreenshot("/wd/hub/session/:sessionId/screenshot/:id")) - var rawResp rawResponse - if rawResp, err = ue.parent.httpGET("/session", ue.parent.sessionId, "/element", ue.id, "/screenshot"); err != nil { - return nil, err - } - reply := new(struct{ Value string }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return nil, err - } - - var decodeStr []byte - if decodeStr, err = base64.StdEncoding.DecodeString(reply.Value); err != nil { - return nil, err - } - - raw = bytes.NewBuffer(decodeStr) - return -} diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index 21deeaf3..aa119e0f 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -3,6 +3,8 @@ package uixt import ( + "encoding/json" + "fmt" "io/ioutil" "testing" "time" @@ -115,20 +117,6 @@ func TestDriver_Screenshot(t *testing.T) { t.Log(ioutil.WriteFile("/Users/hero/Desktop/s1.png", screenshot.Bytes(), 0o600)) } -func TestDriver_Orientation(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - orientation, err := driver.Orientation() - if err != nil { - t.Fatal(err) - } - - t.Log(orientation) -} - func TestDriver_Rotation(t *testing.T) { driver, err := NewUIADriver(nil, uiaServerURL) if err != nil { @@ -232,20 +220,6 @@ func TestDriver_DeviceInfo(t *testing.T) { t.Logf("bluetooth state: %s", devInfo.Bluetooth.State) } -func TestDriver_AlertText(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - alertText, err := driver.AlertText() - if err != nil { - t.Fatal(err) - } - - t.Log(alertText) -} - func TestDriver_Tap(t *testing.T) { driver, err := NewUIADriver(nil, uiaServerURL) if err != nil { @@ -326,333 +300,13 @@ func TestDriver_SendKeys(t *testing.T) { } } -//func TestDriver_PressBack(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// err = driver.PressBack() -// if err != nil { -// t.Fatal(err) -// } -//} - -//func TestDriver_PressKeyCode(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// err = driver.PressKeyCodeAsync(KCx) -// if err != nil { -// t.Fatal(err) -// } -// err = driver.PressKeyCodeAsync(KCx, KMCapLocked) -// if err != nil { -// t.Fatal(err) -// } -// // err = driver.PressKeyCodeAsync(KCExplorer) -// // if err != nil { -// // t.Fatal(err) -// // } -// -// err = driver.PressKeyCode(KCExplorer, KMEmpty) -// if err != nil { -// t.Fatal(err) -// } -//} - -//func TestDriver_LongPressKeyCode(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// err = driver.LongPressKeyCode(KCAt, KMEmpty) -// if err != nil { -// t.Fatal(err) -// } -//} -// -//func TestDriver_TouchDown(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// doTouchUp := func() { -// err = driver.TouchUp(400, 260) -// if err != nil { -// t.Fatal(err) -// } -// } -// -// err = driver.TouchDown(400, 260) -// if err != nil { -// t.Fatal(err) -// } -// -// // _ = driver.TapPoint(Point{400, 500}) -// doTouchUp() -// -// err = driver.TouchDownPoint(Point{400, 260}) -// if err != nil { -// t.Fatal(err) -// } -// -// doTouchUp() -//} -// -//func TestDriver_TouchUp(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// err = driver.TouchDown(400, 260) -// if err != nil { -// t.Fatal(err) -// } -// -// // err = driver.TouchUp(400, 260) -// err = driver.TouchUpPoint(Point{400, 260}) -// if err != nil { -// t.Fatal(err) -// } -//} -// -//func TestDriver_TouchMove(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// doTouchDown := func(x, y int) { -// err = driver.TouchDown(x, y) -// if err != nil { -// t.Fatal(err) -// } -// } -// -// doTouchUp := func(x, y int) { -// err = driver.TouchUp(x, y) -// if err != nil { -// t.Fatal(err) -// } -// } -// -// doTouchDown(400, 260) -// -// err = driver.TouchMove(400, 500) -// if err != nil { -// t.Fatal(err) -// } -// -// doTouchUp(400, 500) -// -// doTouchDown(400, 500) -// -// err = driver.TouchMove(400, 260) -// if err != nil { -// t.Fatal(err) -// } -// -// doTouchUp(400, 260) -//} -// -//func TestDriver_OpenNotification(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// err = driver.OpenNotification() -// if err != nil { -// t.Fatal(err) -// } -//} -// -//func TestDriver_Flick(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// err = driver.Flick(50, -100) -// if err != nil { -// t.Fatal(err) -// } -//} -// -//func TestDriver_ScrollTo(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// err = driver.ScrollTo(BySelector{ClassName: "android.widget.SeekBar"}) -// if err != nil { -// t.Fatal(err) -// } -//} - -//func TestDriver_MultiPointerGesture(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// gesture1 := NewTouchAction().Add(150, 340, 0.35).AddFloat(50, 300) -// gesture2 := NewTouchAction().Add(200, 340).AddFloat(300, 300) -// gesture3 := NewTouchAction().Add(300, 500).AddFloat(350, 500).AddPoint(Point{300, 550}).AddPointF(PointF{350, 550}) -// _ = gesture3 -// -// // err = driver.MultiPointerGesture(gesture1, gesture2) -// err = driver.MultiPointerGesture(gesture1, gesture2, gesture3) -// if err != nil { -// t.Fatal(err) -// } -//} -// -//func TestDriver_PerformW3CActions(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// // actionKey := NewW3CAction(ATKey, NewW3CGestures().KeyDown("g").KeyUp("g").Pause().KeyDown("o").KeyUp("o")) -// // actionKey := NewW3CAction(ATKey, NewW3CGestures().SendKeys("golang")) -// // err = driver.PerformW3CActions(actionKey) -// // if err != nil { -// // t.Fatal(err) -// // } -// -// // var queryField map[string]string -// // queryField = make(map[string]string) -// // { -// // queryField = map[string]string{ -// // "a": "", -// // } -// // } -// -// elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/search"}) -// if err != nil { -// t.Fatal(err) -// } -// // actionPointer := NewW3CAction(ATPointer, NewW3CGestures().PointerMove(0, 0, elem.id).PointerDown().Pause(3).PointerUp()) -// // actionPointer := NewW3CAction(ATPointer, -// // NewW3CGestures().PointerMove(400, 500, "viewport").PointerDown().Pause(2). -// // PointerMove(0, 0, elem.id).Pause(2). -// // PointerMove(20, 0, "pointer").Pause(2). -// // PointerUp(), -// // ) -// actionPointer := NewW3CAction(ATPointer, -// NewW3CGestures().PointerMoveTo(400, 500).PointerDown(). -// PointerMouseOver(0, 0, elem). -// PointerMoveRelative(20, 0).PointerUp()) -// err = driver.PerformW3CActions(actionPointer) -// if err != nil { -// t.Fatal(err) -// } -//} -// -//func TestDriver_GetClipboard(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// text, err := driver.GetClipboard() -// if err != nil { -// t.Fatal(err) -// } -// t.Log(text) -//} -// -//func TestDriver_SetClipboard(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// content := "test123" -// err = driver.SetClipboard(ClipDataTypePlaintext, content) -// if err != nil { -// t.Fatal(err) -// } -// -// text, err := driver.GetClipboard() -// if err != nil { -// t.Fatal(err) -// } -// if text != content { -// t.Fatal("should be the same") -// } -//} - -func TestDriver_AlertAccept(t *testing.T) { +func TestDriver_PressBack(t *testing.T) { driver, err := NewUIADriver(nil, uiaServerURL) if err != nil { t.Fatal(err) } - err = driver.AlertAccept() - // err = driver.AlertAccept("是") - if err != nil { - t.Fatal(err) - } -} - -func TestDriver_AlertDismiss(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - // err = driver.AlertDismiss() - err = driver.AlertDismiss("否") - if err != nil { - t.Fatal(err) - } -} - -//func TestDriver_SetAppiumSettings(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// appiumSettings, err := driver.GetAppiumSettings() -// if err != nil { -// t.Fatal(err) -// } -// sdopd := appiumSettings["shutdownOnPowerDisconnect"] -// t.Log("shutdownOnPowerDisconnect:", sdopd) -// -// err = driver.SetAppiumSettings(map[string]interface{}{"shutdownOnPowerDisconnect": !sdopd.(bool)}) -// if err != nil { -// t.Fatal(err) -// } -// -// appiumSettings, err = driver.GetAppiumSettings() -// if err != nil { -// t.Fatal(err) -// } -// if appiumSettings["shutdownOnPowerDisconnect"] == sdopd.(bool) { -// t.Fatal("should not be equal") -// } -// t.Log("shutdownOnPowerDisconnect:", appiumSettings["shutdownOnPowerDisconnect"]) -//} - -func TestDriver_SetOrientation(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - err = driver.SetOrientation(OrientationLandscapeLeft) - // err = driver.SetOrientation(OrientationPortrait) + err = driver.PressBack() if err != nil { t.Fatal(err) } @@ -671,118 +325,6 @@ func TestDriver_SetRotation(t *testing.T) { } } -//func TestDriver_NetworkConnection(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// err = driver.NetworkConnection(NetworkTypeWifi) -// if err != nil { -// t.Fatal(err) -// } -//} - -func TestDriver_FindElement(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "android:id/content"}) - if err != nil { - t.Fatal(err) - } - e := ElementAttribute{}.WithLabel("class") - t.Log(elem.GetAttribute(e)) -} - -func TestDriver_FindElements(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - // elements, err := driver.FindElements(BySelector{ResourceIdID: "com.android.settings:id/title"}) - elements, err := driver.FindElements(BySelector{UiAutomator: "new UiSelector().textStartsWith(\"应\");"}) - if err != nil { - t.Fatal(err) - } - t.Log(len(elements)) -} - -func TestDriver_WaitWithTimeoutAndInterval(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - element, err := driver.FindElement(BySelector{UiAutomator: "new UiSelector().className(\"android.view.ViewGroup\");"}) - if err != nil { - t.Fatal(err) - } - - elem, err := element.FindElement(BySelector{UiAutomator: "new UiSelector().className(\"android.widget.LinearLayout\").index(6);"}) - if err != nil { - t.Fatal(err) - } - - rect, err := elem.Rect() - if err != nil { - t.Fatal(err) - } - - x := rect.X + int(float64(rect.Width)*2) - y := rect.Y + rect.Height/2 - err = driver.Tap(x, y) - if err != nil { - t.Fatal(err) - } - - by := BySelector{UiAutomator: "new UiSelector().text(\"科技\");"} - exists := func(d WebDriver) (bool, error) { - element, err = d.FindElement(by) - if err == nil { - return true, nil - } - return false, nil - } - - err = driver.WaitWithTimeoutAndInterval(exists, 1, 1) - if err != nil { - t.Fatal(err) - } - - // element, err = driver.FindElement(by) - // if err != nil { - // t.Fatal(err) - // } - - err = element.Click() - if err != nil { - t.Fatal(err) - } -} - -//func TestDriver_ActiveElement(t *testing.T) { -// device, _ := NewAndroidDevice() -// driver, err := device.NewUSBDriver(nil) -// if err != nil { -// t.Fatal(err) -// } -// defer func() { -// _ = driver.Dispose() -// }() -// -// element, err := driver.ActiveElement() -// if err != nil { -// t.Fatal(err) -// } -// -// if err = element.SendKeys("test"); err != nil { -// t.Fatal(err) -// } -//} - func TestUiSelectorHelper_NewUiSelectorHelper(t *testing.T) { uiSelector := NewUiSelectorHelper().Text("a").String() if uiSelector != `new UiSelector().text("a");` { @@ -828,39 +370,6 @@ func TestDeviceList(t *testing.T) { } } -//func TestAndroidNewUSBDriver(t *testing.T) { -// device, _ := NewAndroidDevice() -// driver, err := device.NewUSBDriver(nil) -// if err != nil { -// t.Fatal(err) -// } -// defer driver.Dispose() -// -// ready, err := driver.Status() -// if err != nil { -// t.Fatal(err) -// } -// if !ready { -// t.Fatal("should be 'true'") -// } -//} - -//func TestDriver_ActiveAppPackageName(t *testing.T) { -// device, _ := NewAndroidDevice() -// driver, err := device.NewUSBDriver(nil) -// if err != nil { -// t.Fatal(err) -// } -// defer driver.Dispose() -// -// appPackageName, err := driver.ActiveAppPackageName() -// if err != nil { -// t.Fatal(err) -// } -// -// t.Log(appPackageName) -//} - func TestDriver_AppLaunch(t *testing.T) { device, _ := NewAndroidDevice() driver, err := device.NewUSBDriver(nil) @@ -912,7 +421,6 @@ func TestDriver_AppTerminate(t *testing.T) { if err != nil { t.Fatal(err) } - defer driver.Dispose() _, err = driver.AppTerminate("tv.danmaku.bili") if err != nil { @@ -920,413 +428,12 @@ func TestDriver_AppTerminate(t *testing.T) { } } -//func TestNewWiFiDriver(t *testing.T) { -// device, _ := NewAndroidDevice(WithAdbIP("192.168.1.28")) -// driver, err := device.NewHTTPDriver(nil) -// if err != nil { -// t.Fatal(err) -// } -// -// // SetDebug(false, true) -// _, err = driver.ActiveAppActivity() -// if err != nil { -// t.Fatal(err) -// } -//} - -//func TestDriver_AppInstall(t *testing.T) { -// device, _ := NewAndroidDevice() -// driver, err := device.NewUSBDriver(nil) -// if err != nil { -// t.Fatal(err) -// } -// defer driver.Dispose() -// -// err = driver.AppInstall("/Users/hero/Desktop/xuexi_android_10002068.apk") -// if err != nil { -// t.Fatal(err) -// } -//} - -//func TestDriver_AppUninstall(t *testing.T) { -// device, _ := NewAndroidDevice() -// driver, err := device.NewUSBDriver(nil) -// if err != nil { -// t.Fatal(err) -// } -// defer driver.Dispose() -// -// err = driver.AppUninstall("cn.xuexi.android") -// if err != nil { -// t.Fatal(err) -// } -//} - -func TestBySelector_getMethodAndSelector(t *testing.T) { - testVal := "test id" - bySelector := BySelector{ResourceIdID: testVal} - method, selector := bySelector.getMethodAndSelector() - if method != "id" || selector != testVal { - t.Fatal(method, "=", selector) - } - - bySelector = BySelector{ContentDescription: testVal} - method, selector = bySelector.getMethodAndSelector() - if method != "accessibility id" || selector != testVal { - t.Fatal(method, "=", selector) +func TestConvertPoints(t *testing.T) { + data := "10-09 20:16:48.216 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317808206,\"ext\":\"输入\",\"from\":{\"x\":0.0,\"y\":0.0},\"operation\":\"Gtf-SendKeys\",\"run_time\":627,\"start\":1665317807579,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":0.0,\"y\":0.0}}\n10-09 20:18:22.899 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317902898,\"ext\":\"进入直播间\",\"from\":{\"x\":717.0,\"y\":2117.5},\"operation\":\"Gtf-Tap\",\"run_time\":121,\"start\":1665317902777,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":717.0,\"y\":2117.5}}\n10-09 20:18:32.063 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317912062,\"ext\":\"第一次上划\",\"from\":{\"x\":1437.0,\"y\":2409.9},\"operation\":\"Gtf-Swipe\",\"run_time\":32,\"start\":1665317912030,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":1437.0,\"y\":2409.9}}" + eps := ConvertPoints(data) + if len(eps) != 3 { + t.Fatal() } + jsons, _ := json.Marshal(eps) + println(fmt.Sprintf("%v", string(jsons))) } - -func TestElement_Text(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/category_title"}) - if err != nil { - t.Fatal(err) - } - - text, err := elem.Text() - if err != nil { - t.Fatal(err) - } - - t.Log(text) -} - -func TestElement_GetAttribute(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/category_title"}) - if err != nil { - t.Fatal(err) - } - - e := ElementAttribute{}.WithName("class") - attribute, err := elem.GetAttribute(e) - if err != nil { - t.Fatal(err) - } - - t.Log(attribute) -} - -//func TestElement_ContentDescription(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/search"}) -// if err != nil { -// t.Fatal(err) -// } -// -// name, err := elem.ContentDescription() -// if err != nil { -// t.Fatal(err) -// } -// -// t.Log(name) -//} - -func TestElement_Size(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/search"}) - if err != nil { - t.Fatal(err) - } - - size, err := elem.Size() - if err != nil { - t.Fatal(err) - } - - t.Log(size) -} - -func TestElement_Rect(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/category_title"}) - if err != nil { - t.Fatal(err) - } - - rect, err := elem.Rect() - if err != nil { - t.Fatal(err) - } - - t.Log(rect) -} - -func TestElement_Screenshot(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/category_title"}) - if err != nil { - t.Fatal(err) - } - - screenshot, err := elem.Screenshot() - if err != nil { - t.Fatal(err) - } - - t.Log(ioutil.WriteFile("/Users/hero/Desktop/e1.png", screenshot.Bytes(), 0o600)) -} - -func TestElement_Location(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/category_title"}) - if err != nil { - t.Fatal(err) - } - - location, err := elem.Location() - if err != nil { - t.Fatal(err) - } - - t.Log(location) -} - -func TestElement_Click(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/title"}) - if err != nil { - t.Fatal(err) - } - - err = elem.Click() - if err != nil { - t.Fatal(err) - } -} - -func TestElement_Clear(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "android:id/search_src_text"}) - if err != nil { - t.Fatal(err) - } - - err = elem.Clear() - if err != nil { - t.Fatal(err) - } -} - -func TestElement_SendKeys(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "android:id/search_src_text"}) - if err != nil { - t.Fatal(err) - } - - // return - - // err = elem.SendKeys("abc") - err = elem.SendKeys("456") - if err != nil { - t.Fatal(err) - } -} - -func TestElement_FindElements(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - parentElem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/main_content"}) - if err != nil { - t.Fatal(err) - } - - elements, err := parentElem.FindElements(BySelector{ResourceIdID: "com.android.settings:id/category"}) - if err != nil { - t.Fatal(err) - } - t.Log(len(elements)) -} - -func TestElement_FindElement(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - parentElem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/main_content"}) - if err != nil { - t.Fatal(err) - } - - elem, err := parentElem.FindElement(BySelector{ResourceIdID: "com.android.settings:id/category_title"}) - if err != nil { - t.Fatal(err) - } - - t.Log(elem.Text()) -} - -func TestElement_Swipe(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - elem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/category_title"}) - if err != nil { - t.Fatal(err) - } - - rect, err := elem.Rect() - if err != nil { - t.Fatal(err) - } - - t.Log(rect) - - var startX, startY, endX, endY int - startX = rect.X + rect.Width/20 - startY = rect.Y + rect.Height/2 - endX = startX - endY = startY - startY/2 - err = elem.Swipe(startX, startY, endX, endY) - if err != nil { - t.Fatal(err) - } -} - -//func TestElement_Drag(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// elements, err := driver.FindElements(BySelector{ClassName: "android.widget.TextView"}) -// if err != nil { -// t.Fatal(err) -// } -// -// for i, elem := range elements { -// text, _ := elem.Text() -// t.Log(i, text) -// } -// -// rect, err := elements[0].Rect() -// if err != nil { -// t.Fatal(err) -// } -// -// // err = elements[0].Drag(300, 450, 256) -// err = elements[0].Drag(300, 450, 256) -// if err != nil { -// t.Fatal(err) -// } -// -// err = elements[0].DragTo(elements[1], 256) -// if err != nil { -// t.Fatal(err) -// } -// -// endPoint := PointF{X: float64(rect.X + rect.Width/3*2), Y: float64(rect.Y + rect.Height/2)} -// err = elements[0].DragPointF(endPoint, 256) -// if err != nil { -// t.Fatal() -// } -//} - -//func TestElement_Flick(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// elem, err := driver.FindElement(BySelector{UiAutomator: "new UiSelector().text(\"提示音和通知\");"}) -// if err != nil { -// t.Fatal(err) -// } -// -// err = elem.Flick(36, 20, 100) -// if err != nil { -// t.Fatal(err) -// } -//} - -//func TestElement_ScrollTo(t *testing.T) { -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// // how to make it work? -// // parentElem, err := driver.FindElement(BySelector{ClassName: "android.widget.ScrollView"}) -// // parentElem, err := driver.FindElement(BySelector{ResourceIdID: "com.cyanogenmod.filemanager:id/navigation_view_layout"}) -// parentElem, err := driver.FindElement(BySelector{ResourceIdID: "com.android.settings:id/dashboard"}) -// if err != nil { -// t.Fatal(err) -// } -// -// err = parentElem.ScrollTo(BySelector{ContentDescription: "电池"}) -// if err != nil { -// t.Fatal(err) -// } -//} - -//func TestElement_ScrollToElement(t *testing.T) { -// // android.widget.HorizontalScrollView -// driver, err := NewUIADriver(nil, uiaServerURL) -// if err != nil { -// t.Fatal(err) -// } -// -// // how to make it work? -// parentElem, err := driver.FindElement(BySelector{UiAutomator: "new UiSelector().resourceId(\"com.android.settings:id/dashboard\");"}) -// if err != nil { -// t.Fatal(err) -// } -// -// element, err := driver.FindElement(BySelector{UiAutomator: "new UiSelector().text(\"电池\");"}) -// if err != nil { -// t.Fatal(err) -// } -// -// err = parentElem.ScrollToElement(element) -// if err != nil { -// t.Fatal(err) -// } -//} diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 67b21c52..d87e7b18 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -7,10 +7,12 @@ import ( "image" "image/jpeg" "image/png" + "mime" + "mime/multipart" + "net/http" "os" "path/filepath" "strings" - "testing" "time" "github.com/httprunner/httprunner/v4/hrp/internal/builtin" @@ -22,19 +24,18 @@ import ( type MobileMethod string const ( - AppInstall MobileMethod = "install" - AppUninstall MobileMethod = "uninstall" - AppStart MobileMethod = "app_start" - AppLaunch MobileMethod = "app_launch" // 等待 app 打开并堵塞到 app 首屏加载完成,可以传入 app 的启动参数、环境变量 - AppLaunchUnattached MobileMethod = "app_launch_unattached" // 只负责通知打开 app,不堵塞等待,不可传入启动参数 - AppTerminate MobileMethod = "app_terminate" - AppStop MobileMethod = "app_stop" - CtlScreenShot MobileMethod = "screenshot" - CtlSleep MobileMethod = "sleep" - CtlStartCamera MobileMethod = "camera_start" // alias for app_launch camera - CtlStopCamera MobileMethod = "camera_stop" // alias for app_terminate camera - RecordStart MobileMethod = "record_start" - RecordStop MobileMethod = "record_stop" + AppInstall MobileMethod = "install" + AppUninstall MobileMethod = "uninstall" + AppStart MobileMethod = "app_start" + AppLaunch MobileMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成 + AppTerminate MobileMethod = "app_terminate" + AppStop MobileMethod = "app_stop" + CtlScreenShot MobileMethod = "screenshot" + CtlSleep MobileMethod = "sleep" + CtlStartCamera MobileMethod = "camera_start" // alias for app_launch camera + CtlStopCamera MobileMethod = "camera_stop" // alias for app_terminate camera + RecordStart MobileMethod = "record_start" + RecordStop MobileMethod = "record_stop" // UI validation SelectorName string = "ui_name" @@ -314,28 +315,6 @@ func isPathExists(path string) bool { return true } -func (dExt *DriverExt) FindUIElement(param string) (ele WebElement, err error) { - var selector BySelector - if strings.HasPrefix(param, "/") { - // xpath - selector = BySelector{ - XPath: param, - } - } else if strings.HasPrefix(param, "com.") { - // name - selector = BySelector{ - ResourceIdID: param, - } - } else { - // name - selector = BySelector{ - LinkText: NewElementAttribute().WithName(param), - } - } - - return dExt.Driver.FindElement(selector) -} - func (dExt *DriverExt) FindUIRectInUIKit(search string, options ...DataOption) (x, y, width, height float64, err error) { // click on text, using OCR if !isPathExists(search) { @@ -351,30 +330,6 @@ func (dExt *DriverExt) MappingToRectInUIKit(rect image.Rectangle) (x, y, width, return } -func (dExt *DriverExt) PerformTouchActions(touchActions *TouchActions) error { - return dExt.Driver.PerformAppiumTouchActions(touchActions) -} - -func (dExt *DriverExt) PerformActions(actions *W3CActions) error { - return dExt.Driver.PerformW3CActions(actions) -} - -func (dExt *DriverExt) IsNameExist(name string) bool { - selector := BySelector{ - LinkText: NewElementAttribute().WithName(name), - } - _, err := dExt.Driver.FindElement(selector) - return err == nil -} - -func (dExt *DriverExt) IsLabelExist(label string) bool { - selector := BySelector{ - LinkText: NewElementAttribute().WithLabel(label), - } - _, err := dExt.Driver.FindElement(selector) - return err == nil -} - func (dExt *DriverExt) IsOCRExist(text string) bool { _, _, _, _, err := dExt.FindTextByOCR(text) return err == nil @@ -400,12 +355,6 @@ func (dExt *DriverExt) DoAction(action MobileAction) error { } return fmt.Errorf("invalid %s params, should be bundleId(string), got %v", AppLaunch, action.Params) - case AppLaunchUnattached: - if bundleId, ok := action.Params.(string); ok { - return dExt.Driver.AppLaunchUnattached(bundleId) - } - return fmt.Errorf("invalid %s params, should be bundleId(string), got %v", - AppLaunchUnattached, action.Params) case ACTION_SwipeToTapApp: if appName, ok := action.Params.(string); ok { return dExt.swipeToTapApp(appName, action) @@ -688,10 +637,6 @@ func (dExt *DriverExt) DoValidation(check, assert, expected string, message ...s } var result bool switch check { - case SelectorName: - result = (dExt.IsNameExist(expected) == exists) - case SelectorLabel: - result = (dExt.IsLabelExist(expected) == exists) case SelectorOCR: result = (dExt.IsOCRExist(expected) == exists) case SelectorImage: @@ -717,12 +662,57 @@ func (dExt *DriverExt) DoValidation(check, assert, expected string, message ...s return true } -func checkErr(t *testing.T, err error, msg ...string) { - if err != nil { - if len(msg) == 0 { - t.Fatal(err) - } else { - t.Fatal(msg, err) - } +func (dExt *DriverExt) ConnectMjpegStream(httpClient *http.Client) (err error) { + if httpClient == nil { + return errors.New(`'httpClient' can't be nil`) } + + var req *http.Request + if req, err = http.NewRequest(http.MethodGet, "http://*", nil); err != nil { + return err + } + + var resp *http.Response + if resp, err = httpClient.Do(req); err != nil { + return err + } + // defer func() { _ = resp.Body.Close() }() + + var boundary string + if _, param, err := mime.ParseMediaType(resp.Header.Get("Content-Type")); err != nil { + return err + } else { + boundary = strings.Trim(param["boundary"], "-") + } + + mjpegReader := multipart.NewReader(resp.Body, boundary) + + go func() { + for { + select { + case <-dExt.doneMjpegStream: + _ = resp.Body.Close() + return + default: + var part *multipart.Part + if part, err = mjpegReader.NextPart(); err != nil { + dExt.frame = nil + continue + } + + raw := new(bytes.Buffer) + if _, err = raw.ReadFrom(part); err != nil { + dExt.frame = nil + continue + } + dExt.frame = raw + } + } + }() + + return +} + +func (dExt *DriverExt) CloseMjpegStream() { + dExt.doneMjpegStream <- true } diff --git a/hrp/pkg/uixt/gesture.go b/hrp/pkg/uixt/gesture.go deleted file mode 100644 index 7642425c..00000000 --- a/hrp/pkg/uixt/gesture.go +++ /dev/null @@ -1,44 +0,0 @@ -//go:build opencv - -package uixt - -import ( - "image" - "sort" -) - -func (dExt *DriverExt) GesturePassword(pathname string, password ...int) (err error) { - var rects []image.Rectangle - if rects, err = dExt.FindAllImageRect(pathname); err != nil { - return err - } - - sort.Slice(rects, func(i, j int) bool { - if rects[i].Min.Y < rects[j].Min.Y { - return true - } else if rects[i].Min.Y == rects[j].Min.Y { - if rects[i].Min.X < rects[j].Min.X { - return true - } - } - return false - }) - - touchActions := NewTouchActions(len(password)*2 + 1) - for i := range password { - x, y, width, height := dExt.MappingToRectInUIKit(rects[password[i]]) - x = x + width*0.5 - y = y + height*0.5 - - if i == 0 { - touchActions.Press(NewTouchActionPress().WithXYFloat(x, y)). - Wait(0.2) - } else { - touchActions.MoveTo(NewTouchActionMoveTo().WithXYFloat(x, y)). - Wait(0.2) - } - } - touchActions.Release() - - return dExt.PerformTouchActions(touchActions) -} diff --git a/hrp/pkg/uixt/gesture_test.go b/hrp/pkg/uixt/gesture_test.go deleted file mode 100644 index c3b8dcad..00000000 --- a/hrp/pkg/uixt/gesture_test.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build opencv - -package uixt - -import ( - "strconv" - "strings" - "testing" -) - -func TestDriverExt_GesturePassword(t *testing.T) { - split := strings.Split("6304258", "") - password := make([]int, len(split)) - for i := range split { - password[i], _ = strconv.Atoi(split[i]) - } - - driverExt, err := iosDevice.NewDriver(nil) - checkErr(t, err) - - pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_5.png" - - err = driverExt.GesturePassword(pathSearch, password...) - checkErr(t, err) -} diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 3eed2b5b..d324872f 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -4,8 +4,6 @@ import ( "bytes" "fmt" "math" - "reflect" - "strconv" "strings" "time" @@ -30,13 +28,6 @@ func NewCapabilities() Capabilities { return make(Capabilities) } -func (caps Capabilities) WithAppLaunchOption(launchOpt AppLaunchOption) Capabilities { - for k, v := range launchOpt { - caps[k] = v - } - return caps -} - // WithDefaultAlertAction func (caps Capabilities) WithDefaultAlertAction(alertAction AlertAction) Capabilities { caps["defaultAlertAction"] = alertAction @@ -286,44 +277,6 @@ func (v AppState) String() string { } } -// AppLaunchOption Configure app launch parameters -type AppLaunchOption map[string]interface{} - -func NewAppLaunchOption() AppLaunchOption { - return make(AppLaunchOption) -} - -func (opt AppLaunchOption) WithBundleId(bundleId string) AppLaunchOption { - opt["bundleId"] = bundleId - return opt -} - -// WithShouldWaitForQuiescence whether to wait for quiescence on application startup -// Defaults to `true` -func (opt AppLaunchOption) WithShouldWaitForQuiescence(b bool) AppLaunchOption { - opt["shouldWaitForQuiescence"] = b - return opt -} - -// WithArguments The optional array of application command line arguments. -// The arguments are going to be applied if the application was not running before. -func (opt AppLaunchOption) WithArguments(args []string) AppLaunchOption { - opt["arguments"] = args - return opt -} - -// WithEnvironment The optional dictionary of environment variables for the application, which is going to be executed. -// The environment variables are going to be applied if the application was not running before. -func (opt AppLaunchOption) WithEnvironment(env map[string]string) AppLaunchOption { - opt["environment"] = env - return opt -} - -func (opt AppLaunchOption) WithBySelector(bySelector ...BySelector) AppLaunchOption { - opt["bySelector"] = bySelector - return opt -} - // PasteboardType The type of the item on the pasteboard. type PasteboardType string @@ -443,306 +396,6 @@ func (opt SourceOption) WithExcludedAttributes(attributes []string) SourceOption return opt } -const ( - // legacyWebElementIdentifier is the string constant used in the old - // WebDriver JSON protocol that is the key for the map that contains an - // unique element identifier. - legacyWebElementIdentifier = "ELEMENT" - - // webElementIdentifier is the string constant defined by the W3C - // specification that is the key for the map that contains a unique element identifier. - webElementIdentifier = "element-6066-11e4-a52e-4f735466cecf" -) - -func elementIDFromValue(val map[string]string) string { - for _, key := range []string{webElementIdentifier, legacyWebElementIdentifier} { - if v, ok := val[key]; ok && v != "" { - return v - } - } - return "" -} - -// performance ranking: class name > accessibility id > link text > predicate > class chain > xpath -type BySelector struct { - ClassName ElementType `json:"class name"` - - // isSearchByIdentifier - Name string `json:"name"` - Id string `json:"id"` - AccessibilityId string `json:"accessibility id"` - - // partialSearch - LinkText ElementAttribute `json:"link text"` - PartialLinkText ElementAttribute `json:"partial link text"` - // partialSearch - - Predicate string `json:"predicate string"` - - ClassChain string `json:"class chain"` - - XPath string `json:"xpath"` // not recommended, it's slow because it is not supported by XCTest natively - - // Set the search criteria to match the given resource ResourceIdID. - ResourceIdID string `json:"id"` - // Set the search criteria to match the content-description property for a widget. - ContentDescription string `json:"accessibility id"` - - UiAutomator string `json:"-android uiautomator"` -} - -func (wl BySelector) getUsingAndValue() (using, value string) { - vBy := reflect.ValueOf(wl) - tBy := reflect.TypeOf(wl) - for i := 0; i < vBy.NumField(); i++ { - vi := vBy.Field(i).Interface() - switch vi := vi.(type) { - case ElementType: - value = vi.String() - case string: - value = vi - case ElementAttribute: - value = vi.String() - } - if value != "" && value != "UNKNOWN" { - using = tBy.Field(i).Tag.Get("json") - return - } - } - return -} - -func (by BySelector) getMethodAndSelector() (method, selector string) { - vBy := reflect.ValueOf(by) - tBy := reflect.TypeOf(by) - for i := 0; i < vBy.NumField(); i++ { - vi := vBy.Field(i).Interface() - // switch vi := vi.(type) { - // case string: - // selector = vi - // } - selector = vi.(string) - if selector != "" && selector != "UNKNOWN" { - method = tBy.Field(i).Tag.Get("json") - return - } - } - return -} - -type ElementAttribute map[string]interface{} - -func (ea ElementAttribute) String() string { - for k, v := range ea { - switch v := v.(type) { - case bool: - return k + "=" + strconv.FormatBool(v) - case string: - return k + "=" + v - default: - return k + "=" + fmt.Sprintf("%v", v) - } - } - return "UNKNOWN" -} - -func (ea ElementAttribute) getAttributeName() string { - for k := range ea { - return k - } - return "UNKNOWN" -} - -func NewElementAttribute() ElementAttribute { - return make(ElementAttribute) -} - -// WithUID Element's unique identifier -func (ea ElementAttribute) WithUID(uid string) ElementAttribute { - ea["UID"] = uid - return ea -} - -// WithAccessibilityContainer Whether element is an accessibility container -// (contains children of any depth that are accessible) -func (ea ElementAttribute) WithAccessibilityContainer(b bool) ElementAttribute { - ea["accessibilityContainer"] = b - return ea -} - -// WithAccessible Whether element is accessible -func (ea ElementAttribute) WithAccessible(b bool) ElementAttribute { - ea["accessible"] = b - return ea -} - -// WithEnabled Whether element is enabled -func (ea ElementAttribute) WithEnabled(b bool) ElementAttribute { - ea["enabled"] = b - return ea -} - -// WithLabel Element's label -func (ea ElementAttribute) WithLabel(s string) ElementAttribute { - ea["label"] = s - return ea -} - -// WithName Element's name -func (ea ElementAttribute) WithName(s string) ElementAttribute { - ea["name"] = s - return ea -} - -// WithSelected Element's selected state -func (ea ElementAttribute) WithSelected(b bool) ElementAttribute { - ea["selected"] = b - return ea -} - -// WithType Element's type -func (ea ElementAttribute) WithType(elemType ElementType) ElementAttribute { - ea["type"] = elemType - return ea -} - -// WithValue Element's value -func (ea ElementAttribute) WithValue(s string) ElementAttribute { - ea["value"] = s - return ea -} - -// WithVisible -// -// Whether element is visible -func (ea ElementAttribute) WithVisible(b bool) ElementAttribute { - ea["visible"] = b - return ea -} - -func (et ElementType) String() string { - vBy := reflect.ValueOf(et) - tBy := reflect.TypeOf(et) - for i := 0; i < vBy.NumField(); i++ { - if vBy.Field(i).Bool() { - return tBy.Field(i).Tag.Get("json") - } - } - return "UNKNOWN" -} - -// ElementType -// !!! This mapping should be updated if there are changes after each new XCTest release"` -type ElementType struct { - Any bool `json:"XCUIElementTypeAny"` - Other bool `json:"XCUIElementTypeOther"` - Application bool `json:"XCUIElementTypeApplication"` - Group bool `json:"XCUIElementTypeGroup"` - Window bool `json:"XCUIElementTypeWindow"` - Sheet bool `json:"XCUIElementTypeSheet"` - Drawer bool `json:"XCUIElementTypeDrawer"` - Alert bool `json:"XCUIElementTypeAlert"` - Dialog bool `json:"XCUIElementTypeDialog"` - Button bool `json:"XCUIElementTypeButton"` - RadioButton bool `json:"XCUIElementTypeRadioButton"` - RadioGroup bool `json:"XCUIElementTypeRadioGroup"` - CheckBox bool `json:"XCUIElementTypeCheckBox"` - DisclosureTriangle bool `json:"XCUIElementTypeDisclosureTriangle"` - PopUpButton bool `json:"XCUIElementTypePopUpButton"` - ComboBox bool `json:"XCUIElementTypeComboBox"` - MenuButton bool `json:"XCUIElementTypeMenuButton"` - ToolbarButton bool `json:"XCUIElementTypeToolbarButton"` - Popover bool `json:"XCUIElementTypePopover"` - Keyboard bool `json:"XCUIElementTypeKeyboard"` - Key bool `json:"XCUIElementTypeKey"` - NavigationBar bool `json:"XCUIElementTypeNavigationBar"` - TabBar bool `json:"XCUIElementTypeTabBar"` - TabGroup bool `json:"XCUIElementTypeTabGroup"` - Toolbar bool `json:"XCUIElementTypeToolbar"` - StatusBar bool `json:"XCUIElementTypeStatusBar"` - Table bool `json:"XCUIElementTypeTable"` - TableRow bool `json:"XCUIElementTypeTableRow"` - TableColumn bool `json:"XCUIElementTypeTableColumn"` - Outline bool `json:"XCUIElementTypeOutline"` - OutlineRow bool `json:"XCUIElementTypeOutlineRow"` - Browser bool `json:"XCUIElementTypeBrowser"` - CollectionView bool `json:"XCUIElementTypeCollectionView"` - Slider bool `json:"XCUIElementTypeSlider"` - PageIndicator bool `json:"XCUIElementTypePageIndicator"` - ProgressIndicator bool `json:"XCUIElementTypeProgressIndicator"` - ActivityIndicator bool `json:"XCUIElementTypeActivityIndicator"` - SegmentedControl bool `json:"XCUIElementTypeSegmentedControl"` - Picker bool `json:"XCUIElementTypePicker"` - PickerWheel bool `json:"XCUIElementTypePickerWheel"` - Switch bool `json:"XCUIElementTypeSwitch"` - Toggle bool `json:"XCUIElementTypeToggle"` - Link bool `json:"XCUIElementTypeLink"` - Image bool `json:"XCUIElementTypeImage"` - Icon bool `json:"XCUIElementTypeIcon"` - SearchField bool `json:"XCUIElementTypeSearchField"` - ScrollView bool `json:"XCUIElementTypeScrollView"` - ScrollBar bool `json:"XCUIElementTypeScrollBar"` - StaticText bool `json:"XCUIElementTypeStaticText"` - TextField bool `json:"XCUIElementTypeTextField"` - SecureTextField bool `json:"XCUIElementTypeSecureTextField"` - DatePicker bool `json:"XCUIElementTypeDatePicker"` - TextView bool `json:"XCUIElementTypeTextView"` - Menu bool `json:"XCUIElementTypeMenu"` - MenuItem bool `json:"XCUIElementTypeMenuItem"` - MenuBar bool `json:"XCUIElementTypeMenuBar"` - MenuBarItem bool `json:"XCUIElementTypeMenuBarItem"` - Map bool `json:"XCUIElementTypeMap"` - WebView bool `json:"XCUIElementTypeWebView"` - IncrementArrow bool `json:"XCUIElementTypeIncrementArrow"` - DecrementArrow bool `json:"XCUIElementTypeDecrementArrow"` - Timeline bool `json:"XCUIElementTypeTimeline"` - RatingIndicator bool `json:"XCUIElementTypeRatingIndicator"` - ValueIndicator bool `json:"XCUIElementTypeValueIndicator"` - SplitGroup bool `json:"XCUIElementTypeSplitGroup"` - Splitter bool `json:"XCUIElementTypeSplitter"` - RelevanceIndicator bool `json:"XCUIElementTypeRelevanceIndicator"` - ColorWell bool `json:"XCUIElementTypeColorWell"` - HelpTag bool `json:"XCUIElementTypeHelpTag"` - Matte bool `json:"XCUIElementTypeMatte"` - DockItem bool `json:"XCUIElementTypeDockItem"` - Ruler bool `json:"XCUIElementTypeRuler"` - RulerMarker bool `json:"XCUIElementTypeRulerMarker"` - Grid bool `json:"XCUIElementTypeGrid"` - LevelIndicator bool `json:"XCUIElementTypeLevelIndicator"` - Cell bool `json:"XCUIElementTypeCell"` - LayoutArea bool `json:"XCUIElementTypeLayoutArea"` - LayoutItem bool `json:"XCUIElementTypeLayoutItem"` - Handle bool `json:"XCUIElementTypeHandle"` - Stepper bool `json:"XCUIElementTypeStepper"` - Tab bool `json:"XCUIElementTypeTab"` - TouchBar bool `json:"XCUIElementTypeTouchBar"` - StatusItem bool `json:"XCUIElementTypeStatusItem"` - EditText bool `json:"android.widget.EditText"` -} - -// ProtectedResource A system resource that requires user authorization to access. -type ProtectedResource int - -// https://developer.apple.com/documentation/xctest/xcuiprotectedresource?language=objc -const ( - ProtectedResourceContacts ProtectedResource = 1 - ProtectedResourceCalendar ProtectedResource = 2 - ProtectedResourceReminders ProtectedResource = 3 - ProtectedResourcePhotos ProtectedResource = 4 - ProtectedResourceMicrophone ProtectedResource = 5 - ProtectedResourceCamera ProtectedResource = 6 - ProtectedResourceMediaLibrary ProtectedResource = 7 - ProtectedResourceHomeKit ProtectedResource = 8 - ProtectedResourceSystemRootDirectory ProtectedResource = 0x40000000 - ProtectedResourceUserDesktopDirectory ProtectedResource = 0x40000001 - ProtectedResourceUserDownloadsDirectory ProtectedResource = 0x40000002 - ProtectedResourceUserDocumentsDirectory ProtectedResource = 0x40000003 - ProtectedResourceBluetooth ProtectedResource = -0x40000000 - ProtectedResourceKeyboardNetwork ProtectedResource = -0x40000001 - ProtectedResourceLocation ProtectedResource = -0x40000002 - ProtectedResourceHealth ProtectedResource = -0x40000003 -) - type Condition func(wd WebDriver) (bool, error) type Direction string @@ -968,55 +621,16 @@ type WebDriver interface { WindowSize() (Size, error) Screen() (Screen, error) Scale() (float64, error) - ActiveAppInfo() (AppInfo, error) - // ActiveAppsList Retrieves the information about the currently active apps - ActiveAppsList() ([]AppBaseInfo, error) - // AppState Get the state of the particular application in scope of the current session. - // !This method is only returning reliable results since Xcode9 SDK - AppState(bundleId string) (AppState, error) - - // IsLocked Checks if the screen is locked or not. - IsLocked() (bool, error) - // Unlock Forces the device under test to unlock. - // An immediate return will happen if the device is already unlocked - // and an error is going to be thrown if the screen has not been unlocked after the timeout. - Unlock() error - // Lock Forces the device under test to switch to the lock screen. - // An immediate return will happen if the device is already locked - // and an error is going to be thrown if the screen has not been locked after the timeout. - Lock() error // Homescreen Forces the device under test to switch to the home screen Homescreen() error - // AlertText Returns alert's title and description separated by new lines - AlertText() (string, error) - // AlertButtons Gets the labels of the buttons visible in the alert - AlertButtons() ([]string, error) - // AlertAccept Accepts alert, if present - AlertAccept(label ...string) error - // AlertDismiss Dismisses alert, if present - AlertDismiss(label ...string) error - // AlertSendKeys Types a text into an input inside the alert container, if it is present - AlertSendKeys(text string) error - // AppLaunch Launch an application with given bundle identifier in scope of current session. // !This method is only available since Xcode9 SDK - AppLaunch(bundleId string, launchOpt ...AppLaunchOption) error - // AppLaunchUnattached Launch the app with the specified bundle ID. - AppLaunchUnattached(bundleId string) error + AppLaunch(bundleId string) error // AppTerminate Terminate an application with the given bundle id. // Either `true` if the app has been successfully terminated or `false` if it was not running AppTerminate(bundleId string) (bool, error) - // AppActivate Activate an application with given bundle identifier in scope of current session. - // !This method is only available since Xcode9 SDK - AppActivate(bundleId string) error - // AppDeactivate Deactivates application for given time and then activate it again - // The minimum application switch wait is 3 seconds - AppDeactivate(second float64) error - - // AppAuthReset Resets the authorization status for a protected resource. Available since Xcode 11.4 - AppAuthReset(ProtectedResource) error // StartCamera Starts a new camera for recording StartCamera() error @@ -1045,13 +659,6 @@ type WebDriver interface { Swipe(fromX, fromY, toX, toY int, options ...DataOption) error SwipeFloat(fromX, fromY, toX, toY float64, options ...DataOption) error - ForceTouch(x, y int, pressure float64, second ...float64) error - ForceTouchFloat(x, y, pressure float64, second ...float64) error - - // PerformW3CActions Perform complex touch action in scope of the current application. - PerformW3CActions(actions *W3CActions) error - PerformAppiumTouchActions(touchActs *TouchActions) error - // SetPasteboard Sets data to the general pasteboard SetPasteboard(contentType PasteboardType, content string) error // GetPasteboard Gets the data contained in the general pasteboard. @@ -1066,45 +673,12 @@ type WebDriver interface { // Input works like SendKeys Input(text string, options ...DataOption) error - // KeyboardDismiss Tries to dismiss the on-screen keyboard - KeyboardDismiss(keyNames ...string) error - // PressButton Presses the corresponding hardware button on the device PressButton(devBtn DeviceButton) error // PressBack Presses the back button PressBack(options ...DataOption) error - // IOHIDEvent Emulated triggering of the given low-level IOHID device event. - // duration: The event duration in float seconds (XCTest uses 0.005 for a single press event) - IOHIDEvent(pageID EventPageID, usageID EventUsageID, duration ...float64) error - - // ExpectNotification Creates an expectation that is fulfilled when an expected Notification is received - ExpectNotification(notifyName string, notifyType NotificationType, second ...int) error - - // SiriActivate Activates Siri service voice recognition with the given text to parse - SiriActivate(text string) error - // SiriOpenUrl Opens the particular url scheme using Siri voice recognition helpers. - // !This will only work since XCode 8.3/iOS 10.3 - // It doesn't actually work, right? - SiriOpenUrl(url string) error - - Orientation() (Orientation, error) - // SetOrientation Sets requested device interface orientation. - SetOrientation(Orientation) error - - Rotation() (Rotation, error) - // SetRotation Sets the devices orientation to the rotation passed. - SetRotation(Rotation) error - - // MatchTouchID Matches or mismatches TouchID request - MatchTouchID(isMatch bool) error - - // ActiveElement Returns the element, which currently holds the keyboard input focus or nil if there are no such elements. - ActiveElement() (WebElement, error) - FindElement(by BySelector) (WebElement, error) - FindElements(by BySelector) ([]WebElement, error) - Screenshot() (*bytes.Buffer, error) // Source Return application elements tree @@ -1122,13 +696,6 @@ type WebDriver interface { IsHealthy() (bool, error) - // WaitWithTimeoutAndInterval waits for the condition to evaluate to true. - WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.Duration) error - // WaitWithTimeout works like WaitWithTimeoutAndInterval, but with default polling interval. - WaitWithTimeout(condition Condition, timeout time.Duration) error - // Wait works like WaitWithTimeoutAndInterval, but using the default timeout and polling interval. - Wait(condition Condition) error - // Close inner connections properly Close() error @@ -1136,94 +703,3 @@ type WebDriver interface { StartCaptureLog(identifier ...string) (err error) StopCaptureLog() (result interface{}, err error) } - -// WebElement defines method supported by web elements. -type WebElement interface { - // Click Waits for element to become stable (not move) and performs sync tap on element. - Click() error - // SendKeys Types a text into element. It will try to activate keyboard on element, - // if element has no keyboard focus. - // frequency: Frequency of typing (letters per sec). The default value is 60 - SendKeys(text string, options ...DataOption) error - // Clear Clears text on element. It will try to activate keyboard on element, - // if element has no keyboard focus. - Clear() error - - // Tap Waits for element to become stable (not move) and performs sync tap on element, - // relative to the current element position - Tap(x, y int) error - TapFloat(x, y float64) error - - // DoubleTap Sends a double tap event to a hittable point computed for the element. - DoubleTap() error - - // TouchAndHold Sends a long-press gesture to a hittable point computed for the element, - // holding for the specified duration. - // second: The default value is 1 - TouchAndHold(second ...float64) error - // TwoFingerTap Sends a two finger tap event to a hittable point computed for the element. - TwoFingerTap() error - // TapWithNumberOfTaps Sends one or more taps with one or more touch points. - TapWithNumberOfTaps(numberOfTaps, numberOfTouches int) error - // ForceTouch Waits for element to become stable (not move) and performs sync force touch on element. - // second: The default value is 1 - ForceTouch(pressure float64, second ...float64) error - // ForceTouchFloat works like ForceTouch, but relative to the current element position - ForceTouchFloat(x, y, pressure float64, second ...float64) error - - // Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate. - // relative to the current element position - // pressForDuration: The default value is 1 second. - Drag(fromX, fromY, toX, toY int, pressForDuration ...float64) error - DragFloat(fromX, fromY, toX, toY float64, pressForDuration ...float64) error - - // Swipe works like Drag, but `pressForDuration` value is 0. - // relative to the current element position - Swipe(fromX, fromY, toX, toY int) error - SwipeFloat(fromX, fromY, toX, toY float64) error - // SwipeDirection Performs swipe gesture on the element. - // velocity: swipe speed in pixels per second. Custom velocity values are only supported since Xcode SDK 11.4. - SwipeDirection(direction Direction, velocity ...float64) error - - // Pinch Sends a pinching gesture with two touches. - // scale: The scale of the pinch gesture. Use a scale between 0 and 1 to "pinch close" or zoom out - // and a scale greater than 1 to "pinch open" or zoom in. - // velocity: The velocity of the pinch in scale factor per second. - Pinch(scale, velocity float64) error - PinchToZoomOutByW3CAction(scale ...float64) error - - // Rotate Sends a rotation gesture with two touches. - // rotation: The rotation of the gesture in radians. - // velocity: The velocity of the rotation gesture in radians per second. - Rotate(rotation float64, velocity ...float64) error - - // PickerWheelSelect - // offset: The default value is 2 - PickerWheelSelect(order PickerWheelOrder, offset ...int) error - - ScrollElementByName(name string) error - ScrollElementByPredicate(predicate string) error - ScrollToVisible() error - // ScrollDirection - // distance: The default value is 0.5 - ScrollDirection(direction Direction, distance ...float64) error - - FindElement(by BySelector) (element WebElement, err error) - FindElements(by BySelector) (elements []WebElement, err error) - FindVisibleCells() (elements []WebElement, err error) - - Rect() (rect Rect, err error) - Location() (Point, error) - Size() (Size, error) - Text() (text string, err error) - Type() (elemType string, err error) - IsEnabled() (enabled bool, err error) - IsDisplayed() (displayed bool, err error) - IsSelected() (selected bool, err error) - IsAccessible() (accessible bool, err error) - IsAccessibilityContainer() (isAccessibilityContainer bool, err error) - GetAttribute(attr ElementAttribute) (value string, err error) - UID() (uid string) - - Screenshot() (raw *bytes.Buffer, err error) -} diff --git a/hrp/pkg/uixt/ios_action.go b/hrp/pkg/uixt/ios_action.go deleted file mode 100644 index 0541f827..00000000 --- a/hrp/pkg/uixt/ios_action.go +++ /dev/null @@ -1,373 +0,0 @@ -package uixt - -import ( - "strconv" - "strings" -) - -type W3CActions []map[string]interface{} - -func NewW3CActions(capacity ...int) *W3CActions { - if len(capacity) == 0 || capacity[0] <= 0 { - capacity = []int{8} - } - tmp := make(W3CActions, 0, capacity[0]) - return &tmp -} - -func (act *W3CActions) SendKeys(text string) *W3CActions { - keyboard := make(map[string]interface{}) - keyboard["type"] = "key" - keyboard["id"] = "keyboard" + strconv.FormatInt(int64(len(*act)+1), 10) - - ss := strings.Split(text, "") - type KeyEvent struct { - Type string `json:"type"` - Value string `json:"value"` - } - actOptKey := make([]KeyEvent, 0, len(ss)+1) - for i := range ss { - actOptKey = append( - actOptKey, - KeyEvent{Type: "keyDown", Value: ss[i]}, - KeyEvent{Type: "keyUp", Value: ss[i]}, - ) - } - keyboard["actions"] = actOptKey - *act = append(*act, keyboard) - return act -} - -func (act *W3CActions) _newFinger() map[string]interface{} { - pointer := make(map[string]interface{}) - pointer["type"] = "pointer" - pointer["id"] = "finger" + strconv.FormatInt(int64(len(*act)+1), 10) - pointer["parameters"] = map[string]string{"pointerType": "touch"} - return pointer -} - -func (act *W3CActions) FingerAction(fingerAct *FingerAction, fActs ...*FingerAction) *W3CActions { - fActs = append([]*FingerAction{fingerAct}, fActs...) - for i := range fActs { - pointer := act._newFinger() - pointer["actions"] = *fActs[i] - *act = append(*act, pointer) - } - return act -} - -type FingerAction []map[string]interface{} - -func NewFingerAction(capacity ...int) *FingerAction { - if len(capacity) == 0 || capacity[0] <= 0 { - capacity = []int{8} - } - tmp := make(FingerAction, 0, capacity[0]) - return &tmp -} - -type FingerMove map[string]interface{} - -func NewFingerMove() FingerMove { - return map[string]interface{}{"type": "pointerMove"} -} - -func (fm FingerMove) WithXY(x, y int) FingerMove { - fm["x"] = x - fm["y"] = y - return fm -} - -func (fm FingerMove) WithXYFloat(x, y float64) FingerMove { - fm["x"] = x - fm["y"] = y - return fm -} - -func (fm FingerMove) WithOrigin(element WebElement) FingerMove { - fm["origin"] = element.UID() - return fm -} - -func (fm FingerMove) WithDuration(second float64) FingerMove { - fm["duration"] = second - return fm -} - -func (fa *FingerAction) Move(fm FingerMove) *FingerAction { - *fa = append(*fa, fm) - return fa -} - -func (fa *FingerAction) Down() *FingerAction { - *fa = append(*fa, map[string]interface{}{"type": "pointerDown"}) - return fa -} - -func (fa *FingerAction) Up() *FingerAction { - *fa = append(*fa, map[string]interface{}{"type": "pointerUp"}) - return fa -} - -func (fa *FingerAction) Pause(second ...float64) *FingerAction { - if len(second) == 0 || second[0] < 0 { - second = []float64{0.5} - } - tmp := map[string]interface{}{ - "type": "pause", - "duration": second[0] * 1000, - } - *fa = append(*fa, tmp) - return fa -} - -func (act *W3CActions) Tap(x, y int, element ...WebElement) *W3CActions { - fm := NewFingerMove().WithXY(x, y) - if len(element) != 0 { - fm.WithOrigin(element[0]) - } - fingerAction := NewFingerAction(). - Move(fm). - Down(). - Pause(0.1). - Up() - return act.FingerAction(fingerAction) -} - -func (act *W3CActions) DoubleTap(x, y int, element ...WebElement) *W3CActions { - fm := NewFingerMove().WithXY(x, y) - if len(element) != 0 { - fm.WithOrigin(element[0]) - } - fingerAction := NewFingerAction(). - Move(fm). - Down(). - Pause(0.1). - Up(). - Pause(0.04). - Down(). - Pause(0.1). - Up() - return act.FingerAction(fingerAction) -} - -func (act *W3CActions) Press(x, y int, second float64, element ...WebElement) *W3CActions { - fm := NewFingerMove().WithXY(x, y) - if len(element) != 0 { - fm.WithOrigin(element[0]) - } - fingerAction := NewFingerAction(). - Move(fm). - Down(). - Pause(second). - Up() - return act.FingerAction(fingerAction) -} - -func (act *W3CActions) Swipe(fromX, fromY, toX, toY int, element ...WebElement) *W3CActions { - fmFrom := NewFingerMove().WithXY(fromX, fromY) - fmTo := NewFingerMove().WithXY(toX, toY) - if len(element) != 0 { - fmFrom.WithOrigin(element[0]) - fmTo.WithOrigin(element[0]) - } - fingerAction := NewFingerAction(). - Move(fmFrom). - Down(). - Pause(0.25). - Move(fmTo). - Pause(0.25). - Up() - return act.FingerAction(fingerAction) -} - -func (act *W3CActions) SwipeFloat(fromX, fromY, toX, toY float64, element ...WebElement) *W3CActions { - fmFrom := NewFingerMove().WithXYFloat(fromX, fromY) - fmTo := NewFingerMove().WithXYFloat(toX, toY) - if len(element) != 0 { - fmFrom.WithOrigin(element[0]) - fmTo.WithOrigin(element[0]) - } - fingerAction := NewFingerAction(). - Move(fmFrom). - Down(). - Pause(0.25). - Move(fmTo). - Pause(0.25). - Up() - return act.FingerAction(fingerAction) -} - -/* ---------------------------------------------------------------------------------------------------------------- */ - -type TouchActions []map[string]interface{} - -func NewTouchActions(capacity ...int) *TouchActions { - if len(capacity) == 0 || capacity[0] <= 0 { - capacity = []int{8} - } - tmp := make(TouchActions, 0, capacity[0]) - return &tmp -} - -func (act *TouchActions) MoveTo(opt TouchActionMoveTo) *TouchActions { - tmp := map[string]interface{}{ - "action": "moveTo", - "options": opt, - } - *act = append(*act, tmp) - return act -} - -func (act *TouchActions) Tap(opt TouchActionTap) *TouchActions { - tmp := map[string]interface{}{ - "action": "tap", - "options": opt, - } - *act = append(*act, tmp) - return act -} - -func (act *TouchActions) Press(opt TouchActionPress) *TouchActions { - tmp := map[string]interface{}{ - "action": "press", - "options": opt, - } - *act = append(*act, tmp) - return act -} - -func (act *TouchActions) LongPress(opt TouchActionLongPress) *TouchActions { - tmp := map[string]interface{}{ - "action": "longPress", - "options": opt, - } - *act = append(*act, tmp) - return act -} - -func (act *TouchActions) Wait(second ...float64) *TouchActions { - if len(second) == 0 || second[0] < 0 { - second = []float64{0.5} - } - tmp := map[string]interface{}{ - "action": "wait", - "options": map[string]interface{}{"ms": second[0] * 1000}, - } - *act = append(*act, tmp) - return act -} - -func (act *TouchActions) Release() *TouchActions { - tmp := map[string]interface{}{"action": "release"} - *act = append(*act, tmp) - return act -} - -func (act *TouchActions) Cancel() *TouchActions { - tmp := map[string]interface{}{"action": "cancel"} - *act = append(*act, tmp) - return act -} - -type TouchActionMoveTo map[string]interface{} - -func NewTouchActionMoveTo() TouchActionMoveTo { - return make(map[string]interface{}) -} - -func (opt TouchActionMoveTo) WithXY(x, y int) TouchActionMoveTo { - opt["x"] = x - opt["y"] = y - return opt -} - -func (opt TouchActionMoveTo) WithXYFloat(x, y float64) TouchActionMoveTo { - opt["x"] = x - opt["y"] = y - return opt -} - -func (opt TouchActionMoveTo) WithElement(element WebElement) TouchActionMoveTo { - opt["element"] = element.UID() - return opt -} - -type TouchActionTap map[string]interface{} - -func NewTouchActionTap() TouchActionTap { - return make(map[string]interface{}) -} - -func (opt TouchActionTap) WithXY(x, y int) TouchActionTap { - opt["x"] = x - opt["y"] = y - return opt -} - -func (opt TouchActionTap) WithXYFloat(x, y float64) TouchActionTap { - opt["x"] = x - opt["y"] = y - return opt -} - -func (opt TouchActionTap) WithElement(element WebElement) TouchActionTap { - opt["element"] = element.UID() - return opt -} - -func (opt TouchActionTap) WithCount(count int) TouchActionTap { - opt["count"] = count - return opt -} - -type TouchActionPress map[string]interface{} - -func NewTouchActionPress() TouchActionPress { - return make(map[string]interface{}) -} - -func (opt TouchActionPress) WithXY(x, y int) TouchActionPress { - opt["x"] = x - opt["y"] = y - return opt -} - -func (opt TouchActionPress) WithXYFloat(x, y float64) TouchActionPress { - opt["x"] = x - opt["y"] = y - return opt -} - -func (opt TouchActionPress) WithElement(element WebElement) TouchActionPress { - opt["element"] = element.UID() - return opt -} - -func (opt TouchActionPress) WithPressure(pressure float64) TouchActionPress { - opt["pressure"] = pressure - return opt -} - -type TouchActionLongPress map[string]interface{} - -func NewTouchActionLongPress() TouchActionLongPress { - return make(map[string]interface{}) -} - -func (opt TouchActionLongPress) WithXY(x, y int) TouchActionLongPress { - opt["x"] = x - opt["y"] = y - return opt -} - -func (opt TouchActionLongPress) WithXYFloat(x, y float64) TouchActionLongPress { - opt["x"] = x - opt["y"] = y - return opt -} - -func (opt TouchActionLongPress) WithElement(element WebElement) TouchActionLongPress { - opt["element"] = element.UID() - return opt -} diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index 9d301b7b..da8a9a33 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -1,23 +1,16 @@ package uixt import ( - "bytes" "context" - "encoding/base64" - builtinJSON "encoding/json" "fmt" "io" builtinLog "log" - "mime" - "mime/multipart" "net" "net/http" "net/url" "os" "path/filepath" - "regexp" "strconv" - "strings" "time" "github.com/pkg/errors" @@ -25,7 +18,6 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/env" - "github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/pkg/gidevice" ) @@ -687,168 +679,3 @@ func (dev *IOSDevice) RunXCTest(bundleID string) (cancel context.CancelFunc, err return cancel, nil } - -func (dExt *DriverExt) ConnectMjpegStream(httpClient *http.Client) (err error) { - if httpClient == nil { - return errors.New(`'httpClient' can't be nil`) - } - - var req *http.Request - if req, err = http.NewRequest(http.MethodGet, "http://*", nil); err != nil { - return err - } - - var resp *http.Response - if resp, err = httpClient.Do(req); err != nil { - return err - } - // defer func() { _ = resp.Body.Close() }() - - var boundary string - if _, param, err := mime.ParseMediaType(resp.Header.Get("Content-Type")); err != nil { - return err - } else { - boundary = strings.Trim(param["boundary"], "-") - } - - mjpegReader := multipart.NewReader(resp.Body, boundary) - - go func() { - for { - select { - case <-dExt.doneMjpegStream: - _ = resp.Body.Close() - return - default: - var part *multipart.Part - if part, err = mjpegReader.NextPart(); err != nil { - dExt.frame = nil - continue - } - - raw := new(bytes.Buffer) - if _, err = raw.ReadFrom(part); err != nil { - dExt.frame = nil - continue - } - dExt.frame = raw - } - } - }() - - return -} - -func (dExt *DriverExt) CloseMjpegStream() { - dExt.doneMjpegStream <- true -} - -type rawResponse []byte - -func (r rawResponse) checkErr() (err error) { - reply := new(struct { - Value struct { - Err string `json:"error"` - Message string `json:"message"` - Traceback string `json:"traceback"` // wda - Stacktrace string `json:"stacktrace"` // uia - } - }) - if err = json.Unmarshal(r, reply); err != nil { - return err - } - if reply.Value.Err != "" { - errText := reply.Value.Message - re := regexp.MustCompile(`{.+?=(.+?)}`) - if re.MatchString(reply.Value.Message) { - subMatch := re.FindStringSubmatch(reply.Value.Message) - errText = subMatch[len(subMatch)-1] - } - return fmt.Errorf("%s: %s", reply.Value.Err, errText) - } - return -} - -func (r rawResponse) valueConvertToString() (s string, err error) { - reply := new(struct{ Value string }) - if err = json.Unmarshal(r, reply); err != nil { - return "", errors.Wrapf(err, "json.Unmarshal failed, rawResponse: %s", string(r)) - } - s = reply.Value - return -} - -func (r rawResponse) valueConvertToBool() (b bool, err error) { - reply := new(struct{ Value bool }) - if err = json.Unmarshal(r, reply); err != nil { - return false, err - } - b = reply.Value - return -} - -func (r rawResponse) valueConvertToSessionInfo() (sessionInfo SessionInfo, err error) { - reply := new(struct{ Value struct{ SessionInfo } }) - if err = json.Unmarshal(r, reply); err != nil { - return SessionInfo{}, err - } - sessionInfo = reply.Value.SessionInfo - return -} - -func (r rawResponse) valueConvertToJsonRawMessage() (raw builtinJSON.RawMessage, err error) { - reply := new(struct{ Value builtinJSON.RawMessage }) - if err = json.Unmarshal(r, reply); err != nil { - return nil, err - } - raw = reply.Value - return -} - -func (r rawResponse) valueDecodeAsBase64() (raw *bytes.Buffer, err error) { - str, err := r.valueConvertToString() - if err != nil { - return nil, errors.Wrap(err, "failed to convert value to string") - } - decodeString, err := base64.StdEncoding.DecodeString(str) - if err != nil { - return nil, errors.Wrap(err, "failed to decode base64 string") - } - raw = bytes.NewBuffer(decodeString) - return -} - -var errNoSuchElement = errors.New("no such element") - -func (r rawResponse) valueConvertToElementID() (id string, err error) { - reply := new(struct{ Value map[string]string }) - if err = json.Unmarshal(r, reply); err != nil { - return "", err - } - if len(reply.Value) == 0 { - return "", errNoSuchElement - } - if id = elementIDFromValue(reply.Value); id == "" { - return "", fmt.Errorf("invalid element returned: %+v", reply) - } - return -} - -func (r rawResponse) valueConvertToElementIDs() (IDs []string, err error) { - reply := new(struct{ Value []map[string]string }) - if err = json.Unmarshal(r, reply); err != nil { - return nil, err - } - if len(reply.Value) == 0 { - return nil, errNoSuchElement - } - IDs = make([]string, len(reply.Value)) - for i, elem := range reply.Value { - var id string - if id = elementIDFromValue(elem); id == "" { - return nil, fmt.Errorf("invalid element returned: %+v", reply) - } - IDs[i] = id - } - return -} diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index 03f3e213..fc3aede9 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -8,8 +8,8 @@ import ( "net" "net/http" "net/url" + "regexp" "strings" - "time" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -315,12 +315,9 @@ func (wd *wdaDriver) AlertSendKeys(text string) (err error) { return } -func (wd *wdaDriver) AppLaunch(bundleId string, launchOpt ...AppLaunchOption) (err error) { +func (wd *wdaDriver) AppLaunch(bundleId string) (err error) { // [[FBRoute POST:@"/wda/apps/launch"] respondWithTarget:self action:@selector(handleSessionAppLaunch:)] data := make(map[string]interface{}) - if len(launchOpt) != 0 { - data = launchOpt[0] - } data["bundleId"] = bundleId _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/apps/launch") return @@ -363,13 +360,6 @@ func (wd *wdaDriver) AppDeactivate(second float64) (err error) { return } -func (wd *wdaDriver) AppAuthReset(resource ProtectedResource) (err error) { - // [[FBRoute POST:@"/wda/resetAppAuth"] respondWithTarget:self action:@selector(handleResetAppAuth:)] - data := map[string]interface{}{"resource": resource} - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/resetAppAuth") - return -} - func (wd *wdaDriver) Tap(x, y int, options ...DataOption) error { return wd.TapFloat(float64(x), float64(y), options...) } @@ -447,37 +437,6 @@ func (wd *wdaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...DataO return wd.DragFloat(fromX, fromY, toX, toY, options...) } -func (wd *wdaDriver) ForceTouch(x, y int, pressure float64, second ...float64) error { - return wd.ForceTouchFloat(float64(x), float64(y), pressure, second...) -} - -func (wd *wdaDriver) ForceTouchFloat(x, y, pressure float64, second ...float64) error { - if len(second) == 0 || second[0] <= 0 { - second = []float64{1.0} - } - actions := NewTouchActions(). - Press( - NewTouchActionPress().WithXYFloat(x, y).WithPressure(pressure)). - Wait(second[0]). - Release() - return wd.PerformAppiumTouchActions(actions) -} - -func (wd *wdaDriver) PerformW3CActions(actions *W3CActions) (err error) { - // [[FBRoute POST:@"/actions"] respondWithTarget:self action:@selector(handlePerformW3CTouchActions:)] - data := map[string]interface{}{"actions": actions} - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/actions") - return -} - -func (wd *wdaDriver) PerformAppiumTouchActions(touchActs *TouchActions) (err error) { - // [[FBRoute POST:@"/wda/touch/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)] - // [[FBRoute POST:@"/wda/touch/multi/perform"] - data := map[string]interface{}{"actions": touchActs} - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/touch/multi/perform") - return -} - func (wd *wdaDriver) SetPasteboard(contentType PasteboardType, content string) (err error) { // [[FBRoute POST:@"/wda/setPasteboard"] respondWithTarget:self action:@selector(handleSetPasteboard:)] data := map[string]interface{}{ @@ -516,16 +475,6 @@ func (wd *wdaDriver) Input(text string, options ...DataOption) (err error) { return wd.SendKeys(text, options...) } -func (wd *wdaDriver) KeyboardDismiss(keyNames ...string) (err error) { - // [[FBRoute POST:@"/wda/keyboard/dismiss"] respondWithTarget:self action:@selector(handleDismissKeyboardCommand:)] - if len(keyNames) == 0 { - keyNames = []string{"return"} - } - data := map[string]interface{}{"keyNames": keyNames} - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/keyboard/dismiss") - return -} - // PressBack simulates a short press on the BACK button. func (wd *wdaDriver) PressBack(options ...DataOption) (err error) { windowSize, err := wd.WindowSize() @@ -554,20 +503,6 @@ func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) { return } -func (wd *wdaDriver) IOHIDEvent(pageID EventPageID, usageID EventUsageID, duration ...float64) (err error) { - // [[FBRoute POST:@"/wda/performIoHidEvent"] respondWithTarget:self action:@selector(handlePeformIOHIDEvent:)] - if len(duration) == 0 || duration[0] <= 0 { - duration = []float64{0.005} - } - data := map[string]interface{}{ - "page": pageID, - "usage": usageID, - "duration": duration[0], - } - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/performIoHidEvent") - return -} - func (wd *wdaDriver) StartCamera() (err error) { // start camera, alias for app_launch com.apple.camera return wd.AppLaunch("com.apple.camera") @@ -585,34 +520,6 @@ func (wd *wdaDriver) StopCamera() (err error) { return nil } -func (wd *wdaDriver) ExpectNotification(notifyName string, notifyType NotificationType, second ...int) (err error) { - // [[FBRoute POST:@"/wda/expectNotification"] respondWithTarget:self action:@selector(handleExpectNotification:)] - if len(second) == 0 { - second = []int{60} - } - data := map[string]interface{}{ - "name": notifyName, - "type": notifyType, - "timeout": second[0], - } - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/expectNotification") - return -} - -func (wd *wdaDriver) SiriActivate(text string) (err error) { - // [[FBRoute POST:@"/wda/siri/activate"] respondWithTarget:self action:@selector(handleActivateSiri:)] - data := map[string]interface{}{"text": text} - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/siri/activate") - return -} - -func (wd *wdaDriver) SiriOpenUrl(url string) (err error) { - // [[FBRoute POST:@"/url"] respondWithTarget:self action:@selector(handleOpenURL:)] - data := map[string]interface{}{"url": url} - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/url") - return -} - func (wd *wdaDriver) Orientation() (orientation Orientation, err error) { // [[FBRoute GET:@"/orientation"] respondWithTarget:self action:@selector(handleGetOrientation:)] var rawResp rawResponse @@ -654,74 +561,6 @@ func (wd *wdaDriver) SetRotation(rotation Rotation) (err error) { return } -func (wd *wdaDriver) MatchTouchID(isMatch bool) (err error) { - // [FBRoute POST:@"/wda/touch_id"] - data := map[string]interface{}{"match": isMatch} - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/touch_id") - return -} - -func (wd *wdaDriver) ActiveElement() (element WebElement, err error) { - // [[FBRoute GET:@"/element/active"] respondWithTarget:self action:@selector(handleGetActiveElement:)] - var rawResp rawResponse - if rawResp, err = wd.httpGET("/session", wd.sessionId, "/element/active"); err != nil { - return nil, err - } - var elementID string - if elementID, err = rawResp.valueConvertToElementID(); err != nil { - return nil, err - } - element = &wdaElement{parent: wd, id: elementID} - return -} - -func (wd *wdaDriver) FindElement(by BySelector) (element WebElement, err error) { - // [[FBRoute POST:@"/element"] respondWithTarget:self action:@selector(handleFindElement:)] - using, value := by.getUsingAndValue() - data := map[string]interface{}{ - "using": using, - "value": value, - } - var rawResp rawResponse - if rawResp, err = wd.httpPOST(data, "/session", wd.sessionId, "/element"); err != nil { - return nil, err - } - var elementID string - if elementID, err = rawResp.valueConvertToElementID(); err != nil { - if errors.Is(err, errNoSuchElement) { - return nil, fmt.Errorf("%w: unable to find an element using '%s', value '%s'", err, using, value) - } - return nil, err - } - element = &wdaElement{parent: wd, id: elementID} - return -} - -func (wd *wdaDriver) FindElements(by BySelector) (elements []WebElement, err error) { - // [[FBRoute POST:@"/elements"] respondWithTarget:self action:@selector(handleFindElements:)] - using, value := by.getUsingAndValue() - data := map[string]interface{}{ - "using": using, - "value": value, - } - var rawResp rawResponse - if rawResp, err = wd.httpPOST(data, "/session", wd.sessionId, "/elements"); err != nil { - return nil, err - } - var elementIDs []string - if elementIDs, err = rawResp.valueConvertToElementIDs(); err != nil { - if errors.Is(err, errNoSuchElement) { - return nil, fmt.Errorf("%w: unable to find an element using '%s', value '%s'", err, using, value) - } - return nil, err - } - elements = make([]WebElement, len(elementIDs)) - for i := range elementIDs { - elements[i] = &wdaElement{parent: wd, id: elementIDs[i]} - } - return -} - func (wd *wdaDriver) Screenshot() (raw *bytes.Buffer, err error) { // [[FBRoute GET:@"/screenshot"] respondWithTarget:self action:@selector(handleGetScreenshot:)] // [[FBRoute GET:@"/screenshot"].withoutSession respondWithTarget:self action:@selector(handleGetScreenshot:)] @@ -838,32 +677,6 @@ func (wd *wdaDriver) WdaShutdown() (err error) { return } -func (wd *wdaDriver) WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.Duration) error { - startTime := time.Now() - for { - done, err := condition(wd) - if err != nil { - return err - } - if done { - return nil - } - - if elapsed := time.Since(startTime); elapsed > timeout { - return fmt.Errorf("timeout after %v", elapsed) - } - time.Sleep(interval) - } -} - -func (wd *wdaDriver) WaitWithTimeout(condition Condition, timeout time.Duration) error { - return wd.WaitWithTimeoutAndInterval(condition, timeout, DefaultWaitInterval) -} - -func (wd *wdaDriver) Wait(condition Condition) error { - return wd.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval) -} - func (wd *wdaDriver) triggerWDALog(data map[string]interface{}) (rawResp []byte, err error) { // [[FBRoute POST:@"/gtf/automation/log"].withoutSession respondWithTarget:self action:@selector(handleAutomationLog:)] return wd.httpPOST(data, "/gtf/automation/log") @@ -907,3 +720,78 @@ func (wd *wdaDriver) StopCaptureLog() (result interface{}, err error) { log.Info().Interface("value", reply.Value).Msg("get WDA log response") return reply.Value, nil } + +type rawResponse []byte + +func (r rawResponse) checkErr() (err error) { + reply := new(struct { + Value struct { + Err string `json:"error"` + Message string `json:"message"` + Traceback string `json:"traceback"` // wda + Stacktrace string `json:"stacktrace"` // uia + } + }) + if err = json.Unmarshal(r, reply); err != nil { + return err + } + if reply.Value.Err != "" { + errText := reply.Value.Message + re := regexp.MustCompile(`{.+?=(.+?)}`) + if re.MatchString(reply.Value.Message) { + subMatch := re.FindStringSubmatch(reply.Value.Message) + errText = subMatch[len(subMatch)-1] + } + return fmt.Errorf("%s: %s", reply.Value.Err, errText) + } + return +} + +func (r rawResponse) valueConvertToString() (s string, err error) { + reply := new(struct{ Value string }) + if err = json.Unmarshal(r, reply); err != nil { + return "", errors.Wrapf(err, "json.Unmarshal failed, rawResponse: %s", string(r)) + } + s = reply.Value + return +} + +func (r rawResponse) valueConvertToBool() (b bool, err error) { + reply := new(struct{ Value bool }) + if err = json.Unmarshal(r, reply); err != nil { + return false, err + } + b = reply.Value + return +} + +func (r rawResponse) valueConvertToSessionInfo() (sessionInfo SessionInfo, err error) { + reply := new(struct{ Value struct{ SessionInfo } }) + if err = json.Unmarshal(r, reply); err != nil { + return SessionInfo{}, err + } + sessionInfo = reply.Value.SessionInfo + return +} + +func (r rawResponse) valueConvertToJsonRawMessage() (raw builtinJSON.RawMessage, err error) { + reply := new(struct{ Value builtinJSON.RawMessage }) + if err = json.Unmarshal(r, reply); err != nil { + return nil, err + } + raw = reply.Value + return +} + +func (r rawResponse) valueDecodeAsBase64() (raw *bytes.Buffer, err error) { + str, err := r.valueConvertToString() + if err != nil { + return nil, errors.Wrap(err, "failed to convert value to string") + } + decodeString, err := base64.StdEncoding.DecodeString(str) + if err != nil { + return nil, errors.Wrap(err, "failed to decode base64 string") + } + raw = bytes.NewBuffer(decodeString) + return +} diff --git a/hrp/pkg/uixt/ios_element.go b/hrp/pkg/uixt/ios_element.go deleted file mode 100644 index 02f54aac..00000000 --- a/hrp/pkg/uixt/ios_element.go +++ /dev/null @@ -1,478 +0,0 @@ -package uixt - -import ( - "bytes" - "fmt" - "math" - "strings" - - "github.com/pkg/errors" - - "github.com/httprunner/httprunner/v4/hrp/internal/json" -) - -// All elements returned by search endpoints have assigned element_id. -// Given element_id you can query properties like: -// enabled, rect, size, location, text, displayed, accessible, name -type wdaElement struct { - parent *wdaDriver - id string // element_id -} - -func (we wdaElement) Click() (err error) { - // [[FBRoute POST:@"/element/:uuid/click"] respondWithTarget:self action:@selector(handleClick:)] - _, err = we.parent.httpPOST(nil, "/session", we.parent.sessionId, "/element", we.id, "/click") - return -} - -func (we wdaElement) SendKeys(text string, options ...DataOption) (err error) { - // [[FBRoute POST:@"/element/:uuid/value"] respondWithTarget:self action:@selector(handleSetValue:)] - data := map[string]interface{}{ - "value": strings.Split(text, ""), - } - // new data options in post data for extra uiautomator configurations - newData := NewData(data, options...) - - _, err = we.parent.httpPOST(newData, "/session", we.parent.sessionId, "/element", we.id, "/value") - return -} - -func (we wdaElement) Clear() (err error) { - // [[FBRoute POST:@"/element/:uuid/clear"] respondWithTarget:self action:@selector(handleClear:)] - _, err = we.parent.httpPOST(nil, "/session", we.parent.sessionId, "/element", we.id, "/clear") - return -} - -func (we wdaElement) Tap(x, y int) error { - return we.TapFloat(float64(x), float64(y)) -} - -func (we wdaElement) TapFloat(x, y float64) (err error) { - // [[FBRoute POST:@"/wda/tap/:uuid"] respondWithTarget:self action:@selector(handleTap:)] - data := map[string]interface{}{ - "x": x, - "y": y, - } - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/tap/", we.id) - return -} - -func (we wdaElement) DoubleTap() (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/doubleTap"] respondWithTarget:self action:@selector(handleDoubleTap:)] - _, err = we.parent.httpPOST(nil, "/session", we.parent.sessionId, "/wda/element", we.id, "/doubleTap") - return -} - -func (we wdaElement) TouchAndHold(second ...float64) (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/touchAndHold"] respondWithTarget:self action:@selector(handleTouchAndHold:)] - data := make(map[string]interface{}) - if len(second) == 0 || second[0] <= 0 { - second = []float64{1.0} - } - data["duration"] = second[0] - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/element", we.id, "/touchAndHold") - return -} - -func (we wdaElement) TwoFingerTap() (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/twoFingerTap"] respondWithTarget:self action:@selector(handleTwoFingerTap:)] - _, err = we.parent.httpPOST(nil, "/session", we.parent.sessionId, "/wda/element", we.id, "/twoFingerTap") - return -} - -func (we wdaElement) TapWithNumberOfTaps(numberOfTaps, numberOfTouches int) (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/tapWithNumberOfTaps"] respondWithTarget:self action:@selector(handleTapWithNumberOfTaps:)] - if numberOfTouches <= 0 { - return errors.New("'numberOfTouches' must be greater than zero") - } - if numberOfTouches > 5 { - return errors.New("'numberOfTouches' cannot be greater than 5") - } - if numberOfTaps <= 0 { - return errors.New("'numberOfTaps' must be greater than zero") - } - if numberOfTaps > 10 { - return errors.New("'numberOfTaps' cannot be greater than 10") - } - data := map[string]interface{}{ - "numberOfTaps": numberOfTaps, - "numberOfTouches": numberOfTouches, - } - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/element", we.id, "/tapWithNumberOfTaps") - return -} - -func (we wdaElement) ForceTouch(pressure float64, second ...float64) (err error) { - return we.ForceTouchFloat(-1, -1, pressure, second...) -} - -func (we wdaElement) ForceTouchFloat(x, y, pressure float64, second ...float64) (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/forceTouch"] respondWithTarget:self action:@selector(handleForceTouch:)] - data := make(map[string]interface{}) - if x != -1 && y != -1 { - data["x"] = x - data["y"] = y - } - if len(second) == 0 || second[0] <= 0 { - second = []float64{1.0} - } - data["pressure"] = pressure - data["duration"] = second[0] - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/element", we.id, "/forceTouch") - return -} - -func (we wdaElement) Drag(fromX, fromY, toX, toY int, pressForDuration ...float64) error { - return we.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), pressForDuration...) -} - -func (we wdaElement) DragFloat(fromX, fromY, toX, toY float64, pressForDuration ...float64) (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/dragfromtoforduration"] respondWithTarget:self action:@selector(handleDrag:)] - data := map[string]interface{}{ - "fromX": fromX, - "fromY": fromY, - "toX": toX, - "toY": toY, - } - if len(pressForDuration) == 0 || pressForDuration[0] < 0 { - pressForDuration = []float64{1.0} - } - data["duration"] = pressForDuration[0] - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/element", we.id, "/dragfromtoforduration") - return -} - -func (we wdaElement) Swipe(fromX, fromY, toX, toY int) error { - return we.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY)) -} - -func (we wdaElement) SwipeFloat(fromX, fromY, toX, toY float64) error { - return we.DragFloat(fromX, fromY, toX, toY, 0) -} - -func (we wdaElement) SwipeDirection(direction Direction, velocity ...float64) (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/swipe"] respondWithTarget:self action:@selector(handleSwipe:)] - data := map[string]interface{}{"direction": direction} - if len(velocity) != 0 && velocity[0] > 0 { - data["velocity"] = velocity[0] - } - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/element", we.id, "/swipe") - return -} - -func (we wdaElement) Pinch(scale, velocity float64) (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/pinch"] respondWithTarget:self action:@selector(handlePinch:)] - if scale <= 0 { - return errors.New("'scale' must be greater than zero") - } - if scale == 1 { - return errors.New("'scale' must be greater or less than 1") - } - if scale < 1 && velocity > 0 { - return errors.New("'velocity' must be less than zero when 'scale' is less than 1") - } - if scale > 1 && velocity <= 0 { - return errors.New("'velocity' must be greater than zero when 'scale' is greater than 1") - } - data := map[string]interface{}{ - "scale": scale, - "velocity": velocity, - } - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/element", we.id, "/pinch") - return -} - -func (we wdaElement) PinchToZoomOutByW3CAction(scale ...float64) (err error) { - if len(scale) == 0 { - scale = []float64{1.0} - } else if scale[0] > 23 { - scale[0] = 23 - } - var size Size - if size, err = we.Size(); err != nil { - return err - } - r := scale[0] * 2 / 100.0 - offsetX, offsetY := float64(size.Width)*r, float64(size.Height)*r - - actions := NewW3CActions().SwipeFloat(0-offsetX, 0-offsetY, 0, 0, we).SwipeFloat(offsetX, offsetY, 0, 0, we) - return we.parent.PerformW3CActions(actions) -} - -func (we wdaElement) Rotate(rotation float64, velocity ...float64) (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/rotate"] respondWithTarget:self action:@selector(handleRotate:)] - if rotation > math.Pi*2 || rotation < math.Pi*-2 { - return errors.New("'rotation' must not be more than 2π or less than -2π") - } - if len(velocity) == 0 || velocity[0] == 0 { - velocity = []float64{rotation} - } - if rotation > 0 && velocity[0] < 0 || rotation < 0 && velocity[0] > 0 { - return errors.New("'rotation' and 'velocity' must have the same sign") - } - data := map[string]interface{}{ - "rotation": rotation, - "velocity": velocity[0], - } - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/element", we.id, "/rotate") - return -} - -func (we wdaElement) PickerWheelSelect(order PickerWheelOrder, offset ...int) (err error) { - // [[FBRoute POST:@"/wda/pickerwheel/:uuid/select"] respondWithTarget:self action:@selector(handleWheelSelect:)] - if len(offset) == 0 { - offset = []int{2} - } else if offset[0] <= 0 || offset[0] > 5 { - return fmt.Errorf("'offset' value is expected to be in range (0, 5]. '%d' was given instead", offset[0]) - } - data := map[string]interface{}{ - "order": order, - "offset": float64(offset[0]) * 0.1, - } - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/pickerwheel", we.id, "/select") - return -} - -func (we wdaElement) scroll(data interface{}) (err error) { - // [[FBRoute POST:@"/wda/element/:uuid/scroll"] respondWithTarget:self action:@selector(handleScroll:)] - _, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/wda/element", we.id, "/scroll") - return -} - -func (we wdaElement) ScrollElementByName(name string) error { - data := map[string]interface{}{"name": name} - return we.scroll(data) -} - -func (we wdaElement) ScrollElementByPredicate(predicate string) error { - data := map[string]interface{}{"predicateString": predicate} - return we.scroll(data) -} - -func (we wdaElement) ScrollToVisible() error { - data := map[string]interface{}{"toVisible": true} - return we.scroll(data) -} - -func (we wdaElement) ScrollDirection(direction Direction, distance ...float64) error { - if len(distance) == 0 || distance[0] <= 0 { - distance = []float64{0.5} - } - data := map[string]interface{}{ - "direction": direction, - "distance": distance[0], - } - return we.scroll(data) -} - -func (we wdaElement) FindElement(by BySelector) (element WebElement, err error) { - // [[FBRoute POST:@"/element/:uuid/element"] respondWithTarget:self action:@selector(handleFindSubElement:)] - using, value := by.getUsingAndValue() - data := map[string]interface{}{ - "using": using, - "value": value, - } - var rawResp rawResponse - if rawResp, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/element", we.id, "/element"); err != nil { - return nil, err - } - var elementID string - if elementID, err = rawResp.valueConvertToElementID(); err != nil { - if errors.Is(err, errNoSuchElement) { - return nil, fmt.Errorf("%w: unable to find an element using '%s', value '%s'", err, using, value) - } - return nil, err - } - element = &wdaElement{parent: we.parent, id: elementID} - return -} - -func (we wdaElement) FindElements(by BySelector) (elements []WebElement, err error) { - // [[FBRoute POST:@"/element/:uuid/elements"] respondWithTarget:self action:@selector(handleFindSubElements:)] - using, value := by.getUsingAndValue() - data := map[string]interface{}{ - "using": using, - "value": value, - } - var rawResp rawResponse - if rawResp, err = we.parent.httpPOST(data, "/session", we.parent.sessionId, "/element", we.id, "/elements"); err != nil { - return nil, err - } - var elementIDs []string - if elementIDs, err = rawResp.valueConvertToElementIDs(); err != nil { - if errors.Is(err, errNoSuchElement) { - return nil, fmt.Errorf("%w: unable to find an element using '%s', value '%s'", err, using, value) - } - return nil, err - } - elements = make([]WebElement, len(elementIDs)) - for i := range elementIDs { - elements[i] = &wdaElement{parent: we.parent, id: elementIDs[i]} - } - return -} - -func (we wdaElement) FindVisibleCells() (elements []WebElement, err error) { - // [[FBRoute GET:@"/wda/element/:uuid/getVisibleCells"] respondWithTarget:self action:@selector(handleFindVisibleCells:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/wda/element", we.id, "/getVisibleCells"); err != nil { - return nil, err - } - var elementIDs []string - if elementIDs, err = rawResp.valueConvertToElementIDs(); err != nil { - if errors.Is(err, errNoSuchElement) { - return nil, fmt.Errorf("%w: unable to find a cell element in this element", err) - } - return nil, err - } - elements = make([]WebElement, len(elementIDs)) - for i := range elementIDs { - elements[i] = &wdaElement{parent: we.parent, id: elementIDs[i]} - } - return -} - -func (we wdaElement) Rect() (rect Rect, err error) { - // [[FBRoute GET:@"/element/:uuid/rect"] respondWithTarget:self action:@selector(handleGetRect:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/element", we.id, "/rect"); err != nil { - return Rect{}, err - } - reply := new(struct{ Value struct{ Rect } }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return Rect{}, err - } - rect = reply.Value.Rect - return -} - -func (we wdaElement) Location() (Point, error) { - rect, err := we.Rect() - if err != nil { - return Point{}, err - } - return rect.Point, nil -} - -func (we wdaElement) Size() (Size, error) { - rect, err := we.Rect() - if err != nil { - return Size{}, err - } - return rect.Size, nil -} - -func (we wdaElement) Text() (text string, err error) { - // [[FBRoute GET:@"/element/:uuid/text"] respondWithTarget:self action:@selector(handleGetText:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/element", we.id, "/text"); err != nil { - return "", err - } - if text, err = rawResp.valueConvertToString(); err != nil { - return "", err - } - return -} - -func (we wdaElement) Type() (elemType string, err error) { - // [[FBRoute GET:@"/element/:uuid/name"] respondWithTarget:self action:@selector(handleGetName:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/element", we.id, "/name"); err != nil { - return "", err - } - if elemType, err = rawResp.valueConvertToString(); err != nil { - return "", err - } - return -} - -func (we wdaElement) IsEnabled() (enabled bool, err error) { - // [[FBRoute GET:@"/element/:uuid/enabled"] respondWithTarget:self action:@selector(handleGetEnabled:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/element", we.id, "/enabled"); err != nil { - return false, err - } - if enabled, err = rawResp.valueConvertToBool(); err != nil { - return false, err - } - return -} - -func (we wdaElement) IsDisplayed() (displayed bool, err error) { - // [[FBRoute GET:@"/element/:uuid/displayed"] respondWithTarget:self action:@selector(handleGetDisplayed:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/element", we.id, "/displayed"); err != nil { - return false, err - } - if displayed, err = rawResp.valueConvertToBool(); err != nil { - return false, err - } - return -} - -func (we wdaElement) IsSelected() (selected bool, err error) { - // [[FBRoute GET:@"/element/:uuid/selected"] respondWithTarget:self action:@selector(handleGetSelected:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/element", we.id, "/selected"); err != nil { - return false, err - } - if selected, err = rawResp.valueConvertToBool(); err != nil { - return false, err - } - return -} - -func (we wdaElement) IsAccessible() (accessible bool, err error) { - // [[FBRoute GET:@"/wda/element/:uuid/accessible"] respondWithTarget:self action:@selector(handleGetAccessible:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/wda/element", we.id, "/accessible"); err != nil { - return false, err - } - if accessible, err = rawResp.valueConvertToBool(); err != nil { - return false, err - } - return -} - -func (we wdaElement) IsAccessibilityContainer() (isAccessibilityContainer bool, err error) { - // [[FBRoute GET:@"/wda/element/:uuid/accessibilityContainer"] respondWithTarget:self action:@selector(handleGetIsAccessibilityContainer:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/wda/element", we.id, "/accessibilityContainer"); err != nil { - return false, err - } - if isAccessibilityContainer, err = rawResp.valueConvertToBool(); err != nil { - return false, err - } - return -} - -func (we wdaElement) GetAttribute(attr ElementAttribute) (value string, err error) { - // [[FBRoute GET:@"/element/:uuid/attribute/:name"] respondWithTarget:self action:@selector(handleGetAttribute:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/element", we.id, "/attribute", attr.getAttributeName()); err != nil { - return "", err - } - if value, err = rawResp.valueConvertToString(); err != nil { - return "", err - } - return -} - -func (we wdaElement) UID() (uid string) { - return we.id -} - -func (we wdaElement) Screenshot() (raw *bytes.Buffer, err error) { - // W3C element screenshot - // [[FBRoute GET:@"/element/:uuid/screenshot"] respondWithTarget:self action:@selector(handleElementScreenshot:)] - // JSONWP element screenshot - // [[FBRoute GET:@"/screenshot/:uuid"] respondWithTarget:self action:@selector(handleElementScreenshot:)] - var rawResp rawResponse - if rawResp, err = we.parent.httpGET("/session", we.parent.sessionId, "/element", we.id, "/screenshot"); err != nil { - return nil, err - } - if raw, err = rawResp.valueDecodeAsBase64(); err != nil { - return nil, err - } - return -} diff --git a/hrp/pkg/uixt/ios_test.go b/hrp/pkg/uixt/ios_test.go index 07377317..23fff97b 100644 --- a/hrp/pkg/uixt/ios_test.go +++ b/hrp/pkg/uixt/ios_test.go @@ -5,7 +5,6 @@ package uixt import ( "bytes" "fmt" - "math" "testing" "time" ) @@ -69,23 +68,6 @@ func TestNewUSBDriver(t *testing.T) { // t.Log(driver.IsWdaHealthy()) } -func Test_remoteWD_NewSession(t *testing.T) { - setup(t) - - // sessionInfo, err := driver.NewSession(nil) - sessionInfo, err := driver.NewSession( - NewCapabilities().WithAppLaunchOption( - NewAppLaunchOption().WithBundleId(bundleId).WithArguments([]string{"-AppleLanguages", "(Russian)"}), - ), - ) - if err != nil { - t.Fatal(err) - } - if len(sessionInfo.SessionId) == 0 { - t.Fatal(sessionInfo) - } -} - func Test_remoteWD_ActiveSession(t *testing.T) { setup(t) @@ -219,128 +201,6 @@ func Test_remoteWD_Screen(t *testing.T) { t.Log(screen) } -func Test_remoteWD_ActiveAppInfo(t *testing.T) { - setup(t) - - appInfo, err := driver.ActiveAppInfo() - if err != nil { - t.Fatal(err) - } - if len(appInfo.BundleId) == 0 { - t.Fatal(appInfo) - } - t.Log(appInfo) -} - -func Test_remoteWD_ActiveAppsList(t *testing.T) { - setup(t) - - appsList, err := driver.ActiveAppsList() - if err != nil { - t.Fatal(err) - } - if len(appsList) == 0 { - t.Fatal(appsList) - } - t.Log(appsList) -} - -func Test_remoteWD_AppState(t *testing.T) { - setup(t) - - runState, err := driver.AppState(bundleId) - if err != nil { - t.Fatal(err) - } - t.Log(runState) -} - -func Test_remoteWD_IsLocked(t *testing.T) { - setup(t) - - locked, err := driver.IsLocked() - if err != nil { - t.Fatal(err) - } - t.Log(locked) -} - -func Test_remoteWD_Unlock(t *testing.T) { - setup(t) - - err := driver.Unlock() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_Lock(t *testing.T) { - setup(t) - - err := driver.Lock() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_AlertText(t *testing.T) { - setup(t) - - text, err := driver.AlertText() - if err != nil { - t.Fatal(err) - } - _ = text - t.Log(text) -} - -func Test_remoteWD_AlertButtons(t *testing.T) { - setup(t) - - btnLabels, err := driver.AlertButtons() - if err != nil { - t.Fatal(err) - } - t.Log(btnLabels) -} - -func Test_remoteWD_AlertAccept(t *testing.T) { - // Test_remoteWD_AppAuthReset(t) - // return - - setup(t) - - err := driver.AlertAccept() - // err := driver.AlertAccept("") - // err := driver.AlertAccept("好") - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_AlertDismiss(t *testing.T) { - // Test_remoteWD_AppAuthReset(t) - // return - - setup(t) - - err := driver.AlertDismiss() - // err := driver.AlertDismiss("") - // err := driver.AlertDismiss("不允许") - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_AlertSendKeys(t *testing.T) { - setup(t) - - err := driver.AlertSendKeys("todo") - if err != nil { - t.Fatal(err) - } -} - func Test_remoteWD_Homescreen(t *testing.T) { setup(t) @@ -367,15 +227,6 @@ func Test_remoteWD_AppLaunch(t *testing.T) { } } -func Test_remoteWD_AppLaunchUnattached(t *testing.T) { - setup(t) - - err := driver.AppLaunchUnattached(bundleId) - if err != nil { - t.Fatal(err) - } -} - func Test_remoteWD_AppTerminate(t *testing.T) { setup(t) @@ -385,33 +236,6 @@ func Test_remoteWD_AppTerminate(t *testing.T) { } } -func Test_remoteWD_AppActivate(t *testing.T) { - setup(t) - - err := driver.AppActivate(bundleId) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_AppDeactivate(t *testing.T) { - setup(t) - - err := driver.AppDeactivate(2) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_AppAuthReset(t *testing.T) { - setup(t) - - err := driver.AppAuthReset(ProtectedResourceCamera) - if err != nil { - t.Fatal(err) - } -} - func Test_remoteWD_Tap(t *testing.T) { setup(t) @@ -450,15 +274,6 @@ func Test_remoteWD_Drag(t *testing.T) { } } -func Test_remoteWD_ForceTouch(t *testing.T) { - setup(t) - - err := driver.ForceTouch(256, 400, 0.8, -1) - if err != nil { - t.Fatal(err) - } -} - func Test_remoteWD_SetPasteboard(t *testing.T) { setup(t) @@ -524,147 +339,6 @@ func Test_remoteWD_PressButton(t *testing.T) { } } -func Test_remoteWD_SiriActivate(t *testing.T) { - setup(t) - - err := driver.SiriActivate("What's the weather like today") - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_SiriOpenUrl(t *testing.T) { - setup(t) - - err := driver.SiriOpenUrl("Prefs:root=Bluetooth") - // err := driver.SiriOpenUrl("Prefs:root=WIFI![]()") - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_Orientation(t *testing.T) { - setup(t) - - orientation, err := driver.Orientation() - if err != nil { - t.Fatal(err) - } - if orientation == "" { - t.Fatal(orientation) - } -} - -func Test_remoteWD_SetOrientation(t *testing.T) { - setup(t) - - var err error - err = driver.SetOrientation(OrientationLandscapeLeft) - err = driver.SetOrientation(OrientationLandscapeRight) - err = driver.SetOrientation(OrientationPortrait) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_Rotation(t *testing.T) { - setup(t) - - rotation, err := driver.Rotation() - if err != nil { - t.Fatal() - } - t.Log(rotation) -} - -func Test_remoteWD_SetRotation(t *testing.T) { - setup(t) - - err := driver.SetRotation(Rotation{X: 0, Y: 0, Z: 270}) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_PerformW3CActions(t *testing.T) { - // setup(t) - // actions := NewW3CActions().SendKeys("App Store") - - element := setupElement(t, BySelector{Name: "touchableView"}) - actions := NewW3CActions().FingerAction( - NewFingerAction(). - Move(NewFingerMove().WithXY(-15, -85).WithOrigin(element)). - Down(). - Pause(0.25). - Move(NewFingerMove().WithOrigin(element)). - Pause(0.25). - Up(), - NewFingerAction(). - Move(NewFingerMove().WithXY(15, 85).WithOrigin(element)). - Down(). - Pause(0.25). - Move(NewFingerMove().WithOrigin(element)). - Pause(0.25). - Up(), - ) - err := driver.PerformW3CActions(actions) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_PerformAppiumTouchActions(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - actions := NewTouchActions(). - Press(NewTouchActionPress().WithElement(element).WithXY(100, 150).WithPressure(0.2)). - Wait(0.2). - MoveTo(NewTouchActionMoveTo().WithXY(300, 150)). - Wait(0.2). - MoveTo(NewTouchActionMoveTo().WithElement(element)). - Wait(0.2). - MoveTo(NewTouchActionMoveTo().WithElement(element).WithXY(300, 400)). - Release() - - err := driver.PerformAppiumTouchActions(actions) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_ActiveElement(t *testing.T) { - setup(t) - - element, err := driver.ActiveElement() - if err != nil { - t.Fatal(err) - } - _ = element - // t.Log(element) -} - -func Test_remoteWD_FindElement(t *testing.T) { - setup(t) - - element, err := driver.FindElement(BySelector{Name: "设置"}) - if err != nil { - t.Fatal(err) - } - _ = element - // t.Log(element) -} - -func Test_remoteWD_FindElements(t *testing.T) { - setup(t) - - elements, err := driver.FindElements(BySelector{ClassName: ElementType{Icon: true}}) - if err != nil { - t.Fatal(err) - } - _ = elements - t.Log(elements) -} - func Test_remoteWD_Screenshot(t *testing.T) { setup(t) @@ -741,434 +415,3 @@ func Test_remoteWD_AccessibleSource(t *testing.T) { _ = source fmt.Println(source) } - -func Test_remoteWD_Wait(t *testing.T) { - setup(t) - - var element WebElement - var err error - - by := BySelector{Name: "通知"} - // driver.AppLaunch() - exists := func(d WebDriver) (bool, error) { - element, err = d.FindElement(by) - if err == nil { - return true, nil - } - return false, nil - } - _ = exists - _ = element - - err = driver.AppLaunchUnattached(bundleId) - if err != nil { - t.Fatal(err) - } - // element, err = driver.FindElement(by) - err = driver.WaitWithTimeoutAndInterval(exists, time.Second*10, time.Millisecond*10) - if err != nil { - t.Fatal(err) - } - - // t.Log(element.Rect()) -} - -func Test_remoteWD_Location(t *testing.T) { - setup(t) - - location, err := driver.Location() - if err != nil { - t.Fatal(err) - } - t.Log(location) -} - -func Test_remoteWD_KeyboardDismiss(t *testing.T) { - setup(t) - - err := driver.KeyboardDismiss() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWD_ExpectNotification(t *testing.T) { - setup(t) - - // bundleId = "com.apple.shortcuts" - // err := driver.ExpectNotification("shortcuts", NotificationTypePlain, 10) - // if err != nil { - // t.Fatal(err) - // } -} - -func Test_remoteWD_IOHIDEvent(t *testing.T) { - setup(t) - - err := driver.IOHIDEvent(EventPageIDConsumer, EventUsageIDCsmrVolumeDown) - if err != nil { - t.Fatal(err) - } -} - -func setupElement(t *testing.T, by BySelector) WebElement { - setup(t) - element, err := driver.FindElement(by) - if err != nil { - t.Fatal(err) - } - return element -} - -func Test_remoteWE_Click(t *testing.T) { - element := setupElement(t, BySelector{LinkText: NewElementAttribute().WithLabel("设置")}) - - err := element.Click() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_SendKeys(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{SearchField: true}}) - - err := element.SendKeys("App Store") - // err := element.SendKeys("App Store", 3) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_Clear(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{SearchField: true}}) - - err := element.Clear() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_Tap(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - err := element.Tap(10, 20) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_DoubleTap(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - err := element.DoubleTap() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_TouchAndHold(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - err := element.TouchAndHold(-1) - // err := element.TouchAndHold(5) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_TwoFingerTap(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - err := element.TwoFingerTap() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_TapWithNumberOfTaps(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - err := element.TapWithNumberOfTaps(3, 3) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_ForceTouch(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - // err := element.ForceTouch(1, -1) - err := element.ForceTouchFloat(10, 20, 1, -1) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_Drag(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - // err := element.Drag(10, 20, 10, 300, -1) - err := element.Swipe(10, 20, 10, 300) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_SwipeDirection(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - // err := element.SwipeDirection(DirectionUp, -1) - err := element.SwipeDirection(DirectionDown, 120) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_Pinch(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - // zoom in - // err := element.Pinch(2,10) - // zoom out - err := element.Pinch(0.9, -4.5) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_PinchToZoomOutByW3CAction(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - err := element.PinchToZoomOutByW3CAction(15) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_Rotate(t *testing.T) { - element := setupElement(t, BySelector{Name: "touchableView"}) - - // 90 CW - // err := element.Rotate(math.Pi / 2) - // 180 CCW - err := element.Rotate(math.Pi * -2) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_PickerWheelSelect(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{PickerWheel: true}}) - - err := element.PickerWheelSelect(PickerWheelOrderNext, 3) - if err != nil { - t.Fatal(err) - } - err = element.PickerWheelSelect(PickerWheelOrderPrevious) - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_scroll(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Table: true}}) - - var err error - // err = element.ScrollElementByName("电池") - // err = element.ScrollElementByPredicate("type == 'XCUIElementTypeCell' AND name LIKE 'Safari*'") - err = element.ScrollDirection(DirectionDown, 0.8) - - // element, err = driver.FindElement(BySelector{PartialLinkText: NewElementAttribute().WithLabel("Safari")}) - // if err != nil { - // t.Fatal(err) - // } - // err = element.ScrollToVisible() - - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_FindElement(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Table: true}}) - - var err error - element, err = element.FindElement(BySelector{PartialLinkText: NewElementAttribute().WithLabel("Safari")}) - if err != nil { - t.Fatal(err) - } - - err = element.Click() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_FindElements(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Table: true}}) - - elements, err := element.FindElements(BySelector{ClassName: ElementType{Cell: true}}) - if err != nil { - t.Fatal(err) - } - - err = elements[0].Click() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_FindVisibleCells(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Table: true}}) - - cells, err := element.FindVisibleCells() - if err != nil { - t.Fatal(err) - } - - err = cells[0].Click() - if err != nil { - t.Fatal(err) - } -} - -func Test_remoteWE_Rect(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Switch: true}}) - - rect, err := element.Rect() - if err != nil { - t.Fatal(err) - } - location, err := element.Location() - if err != nil { - t.Fatal(err) - } - size, err := element.Size() - if err != nil { - t.Fatal(err) - } - _, _, _ = rect, location, size - t.Log(rect, location, size) -} - -func Test_remoteWE_Text(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Switch: true}}) - - text, err := element.Text() - if err != nil { - t.Fatal(err) - } - _ = text - // t.Log(text) -} - -func Test_remoteWE_Type(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Switch: true}}) - - elemType, err := element.Type() - if err != nil { - t.Fatal(err) - } - _ = elemType - // t.Log(elemType) -} - -func Test_remoteWE_IsEnabled(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Switch: true}}) - - enabled, err := element.IsEnabled() - if err != nil { - t.Fatal(err) - } - _ = enabled - // t.Log(enabled) -} - -func Test_remoteWE_IsDisplayed(t *testing.T) { - element := setupElement(t, BySelector{PartialLinkText: NewElementAttribute().WithLabel("Safari")}) - - displayed, err := element.IsDisplayed() - if err != nil { - t.Fatal(err) - } - _ = displayed - // t.Log(displayed) -} - -func Test_remoteWE_IsSelected(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Switch: true}}) - // element := setupElement(t, BySelector{Name: "添加到主屏幕"}) - // element := setupElement(t, BySelector{Name: "仅App资源库"}) - - selected, err := element.IsSelected() - if err != nil { - t.Fatal(err) - } - _ = selected - // t.Log(selected) -} - -func Test_remoteWE_IsAccessible(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{Switch: true}}) - - accessible, err := element.IsAccessible() - if err != nil { - t.Fatal(err) - } - _ = accessible - // t.Log(accessible) -} - -func Test_remoteWE_IsAccessibilityContainer(t *testing.T) { - // element := setupElement(t, BySelector{ClassName: ElementType{Switch: true}}) - element := setupElement(t, BySelector{ClassName: ElementType{Table: true}}) - - isAccessibilityContainer, err := element.IsAccessibilityContainer() - if err != nil { - t.Fatal(err) - } - _ = isAccessibilityContainer - // t.Log(isAccessibilityContainer) -} - -func Test_remoteWE_GetAttribute(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{StaticText: true}}) - - value, err := element.GetAttribute(NewElementAttribute().WithValue("")) - if err != nil { - t.Fatal(err) - } - _ = value - // t.Log(value) -} - -func Test_remoteWE_Screenshot(t *testing.T) { - element := setupElement(t, BySelector{ClassName: ElementType{TextView: true}}) - - screenshot, err := element.Screenshot() - if err != nil { - t.Fatal(err) - } - _ = screenshot - - // img, format, err := image.Decode(screenshot) - // if err != nil { - // t.Fatal(err) - // } - // userHomeDir, _ := os.UserHomeDir() - // file, err := os.Create(userHomeDir + "/Desktop/e1." + format) - // if err != nil { - // t.Fatal(err) - // } - // defer func() { _ = file.Close() }() - // switch format { - // case "png": - // err = png.Encode(file, img) - // case "jpeg": - // err = jpeg.Encode(file, img, nil) - // } - // if err != nil { - // t.Fatal(err) - // } - // t.Log(file.Name()) -} diff --git a/hrp/pkg/uixt/tap.go b/hrp/pkg/uixt/tap.go index 3d4b584a..5e5cdb70 100644 --- a/hrp/pkg/uixt/tap.go +++ b/hrp/pkg/uixt/tap.go @@ -97,12 +97,6 @@ func (dExt *DriverExt) Tap(param string, options ...DataOption) error { } func (dExt *DriverExt) TapOffset(param string, xOffset, yOffset float64, options ...DataOption) (err error) { - // click on element, find by name attribute - ele, err := dExt.FindUIElement(param) - if err == nil { - return ele.Click() - } - dataOptions := NewDataOptions(options...) x, y, width, height, err := dExt.FindUIRectInUIKit(param, options...) @@ -132,12 +126,6 @@ func (dExt *DriverExt) DoubleTap(param string) (err error) { } func (dExt *DriverExt) DoubleTapOffset(param string, xOffset, yOffset float64) (err error) { - // click on element, find by name attribute - ele, err := dExt.FindUIElement(param) - if err == nil { - return ele.DoubleTap() - } - var x, y, width, height float64 if x, y, width, height, err = dExt.FindUIRectInUIKit(param); err != nil { return err @@ -145,24 +133,3 @@ func (dExt *DriverExt) DoubleTapOffset(param string, xOffset, yOffset float64) ( return dExt.Driver.DoubleTapFloat(x+width*xOffset, y+height*yOffset) } - -// TapWithNumber sends one or more taps -func (dExt *DriverExt) TapWithNumber(param string, numberOfTaps int) (err error) { - return dExt.TapWithNumberOffset(param, numberOfTaps, 0.5, 0.5) -} - -func (dExt *DriverExt) TapWithNumberOffset(param string, numberOfTaps int, xOffset, yOffset float64) (err error) { - if numberOfTaps <= 0 { - numberOfTaps = 1 - } - var x, y, width, height float64 - if x, y, width, height, err = dExt.FindUIRectInUIKit(param); err != nil { - return err - } - - x = x + width*xOffset - y = y + height*yOffset - - touchActions := NewTouchActions().Tap(NewTouchActionTap().WithXYFloat(x, y).WithCount(numberOfTaps)) - return dExt.PerformTouchActions(touchActions) -} diff --git a/hrp/pkg/uixt/tap_test.go b/hrp/pkg/uixt/tap_test.go index c5365998..f0cfd287 100644 --- a/hrp/pkg/uixt/tap_test.go +++ b/hrp/pkg/uixt/tap_test.go @@ -12,17 +12,14 @@ func init() { iosDevice, _ = NewIOSDevice() } -func TestDriverExt_TapWithNumber(t *testing.T) { - driverExt, err := iosDevice.NewDriver(nil) - checkErr(t, err) - - pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/flag7.png" - - err = driverExt.TapWithNumber(pathSearch, 3) - checkErr(t, err) - - err = driverExt.TapWithNumberOffset(pathSearch, 3, 0.5, 0.75) - checkErr(t, err) +func checkErr(t *testing.T, err error, msg ...string) { + if err != nil { + if len(msg) == 0 { + t.Fatal(err) + } else { + t.Fatal(msg, err) + } + } } func TestDriverExt_TapXY(t *testing.T) { diff --git a/hrp/pkg/uixt/touch.go b/hrp/pkg/uixt/touch.go deleted file mode 100644 index fe455507..00000000 --- a/hrp/pkg/uixt/touch.go +++ /dev/null @@ -1,33 +0,0 @@ -package uixt - -func (dExt *DriverExt) ForceTouch(pathname string, pressure float64, duration ...float64) (err error) { - return dExt.ForceTouchOffset(pathname, pressure, 0.5, 0.5, duration...) -} - -func (dExt *DriverExt) ForceTouchOffset(pathname string, pressure, xOffset, yOffset float64, duration ...float64) (err error) { - if len(duration) == 0 { - duration = []float64{1.0} - } - var x, y, width, height float64 - if x, y, width, height, err = dExt.FindUIRectInUIKit(pathname); err != nil { - return err - } - - return dExt.Driver.ForceTouchFloat(x+width*xOffset, y+height*yOffset, pressure, duration[0]) -} - -func (dExt *DriverExt) TouchAndHold(pathname string, duration ...float64) (err error) { - return dExt.TouchAndHoldOffset(pathname, 0.5, 0.5, duration...) -} - -func (dExt *DriverExt) TouchAndHoldOffset(pathname string, xOffset, yOffset float64, duration ...float64) (err error) { - if len(duration) == 0 { - duration = []float64{1.0} - } - var x, y, width, height float64 - if x, y, width, height, err = dExt.FindUIRectInUIKit(pathname); err != nil { - return err - } - - return dExt.Driver.TouchAndHoldFloat(x+width*xOffset, y+height*yOffset, duration[0]) -} diff --git a/hrp/pkg/uixt/touch_test.go b/hrp/pkg/uixt/touch_test.go deleted file mode 100644 index aa5515a8..00000000 --- a/hrp/pkg/uixt/touch_test.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build localtest - -package uixt - -import ( - "testing" -) - -func TestDriverExt_ForceTouch(t *testing.T) { - driverExt, err := iosDevice.NewDriver(nil) - checkErr(t, err) - - pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_ft.png" - - err = driverExt.ForceTouch(pathSearch, 0.5, 3) - checkErr(t, err) - - // err = driverExt.ForceTouchOffset(pathSearch, 0.5, 0.1, 0.9) - // checkErr(t, err) - - // err = driverExt.ForceTouchOffset(pathSearch, 0.2, 1.1, -1) - // checkErr(t, err) -} - -func TestDriverExt_TouchAndHold(t *testing.T) { - driverExt, err := iosDevice.NewDriver(nil) - checkErr(t, err) - - pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_ft.png" - - // err = driverExt.TouchAndHold(pathSearch) - // checkErr(t, err) - - // err = driverExt.TouchAndHold(pathSearch, 3) - // checkErr(t, err) - - err = driverExt.TouchAndHoldOffset(pathSearch, 0.8, 0.1) - checkErr(t, err) -} diff --git a/hrp/runner.go b/hrp/runner.go index 9783b1ae..34a50a7b 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -428,7 +428,7 @@ func (r *CaseRunner) parseConfig() error { } device, err := uixt.NewAndroidDevice(uixt.GetAndroidDeviceOptions(androidDeviceConfig)...) if err != nil { - return errors.Wrap(err, "init iOS device failed") + return errors.Wrap(err, "init Android device failed") } client, err := device.NewDriver(nil) if err != nil { diff --git a/hrp/step_mobile_ui.go b/hrp/step_mobile_ui.go index 657455c5..5d37b203 100644 --- a/hrp/step_mobile_ui.go +++ b/hrp/step_mobile_ui.go @@ -50,14 +50,6 @@ func (s *StepMobile) AppLaunch(bundleId string) *StepMobile { return s } -func (s *StepMobile) AppLaunchUnattached(bundleId string) *StepMobile { - s.mobileStep().Actions = append(s.mobileStep().Actions, uixt.MobileAction{ - Method: uixt.AppLaunchUnattached, - Params: bundleId, - }) - return s -} - func (s *StepMobile) AppTerminate(bundleId string) *StepMobile { s.mobileStep().Actions = append(s.mobileStep().Actions, uixt.MobileAction{ Method: uixt.AppTerminate, diff --git a/hrp/step_mobile_ui_test.go b/hrp/step_mobile_ui_test.go index 814ad3ba..a9337da2 100644 --- a/hrp/step_mobile_ui_test.go +++ b/hrp/step_mobile_ui_test.go @@ -59,7 +59,7 @@ func TestIOSAppLaunch(t *testing.T) { NewStep("终止今日头条"). IOS().AppTerminate("com.ss.iphone.article.News"), NewStep("启动今日头条"). - IOS().AppLaunchUnattached("com.ss.iphone.article.News"), + IOS().AppLaunch("com.ss.iphone.article.News"), }, } err := NewRunner(t).Run(testCase) From 45190462b6fba892a458da96ef5dd7ce4abc1da9 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Mon, 26 Dec 2022 00:35:03 +0800 Subject: [PATCH 9/9] fix: unittest --- examples/data/curl/curl_examples.txt | 16 +++----------- hrp/cmd/convert.go | 32 +++++++++++++++++++++++++--- hrp/pkg/convert/from_postman.go | 16 ++++++++++++-- hrp/pkg/convert/main.go | 20 +++++++++++++++++ 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/examples/data/curl/curl_examples.txt b/examples/data/curl/curl_examples.txt index d6aa3d9f..6d8e6a7e 100644 --- a/examples/data/curl/curl_examples.txt +++ b/examples/data/curl/curl_examples.txt @@ -2,20 +2,10 @@ curl httpbin.org curl https://httpbin.org/get?key1=value1&key2=value2 -curl -H "Content-Type: application/json" \ - -H "Authorization: Bearer b7d03a6947b217efb6f3ec3bd3504582" \ - -d '{"type":"A","name":"www","data":"162.10.66.0","priority":null,"port":null,"weight":null}' \ - "https://httpbin.org/post" +curl -H "Content-Type: application/json" -H "Authorization: Bearer b7d03a6947b217efb6f3ec3bd3504582" -d '{"type":"A","name":"www","data":"162.10.66.0","priority":null,"port":null,"weight":null}' "https://httpbin.org/post" curl -F "dummyName=dummyFile" -F file1=@file1.txt -F file2=@file2.txt https://httpbin.org/post -curl https://httpbin.org/post \ - -d 'shipment[to_address][id]=adr_HrBKVA85' \ - -d 'shipment[from_address][id]=adr_VtuTOj7o' \ - -d 'shipment[parcel][id]=prcl_WDv2VzHp' \ - -d 'shipment[is_return]=true' \ - -d 'shipment[customs_info][id]=cstinfo_bl5sE20Y' - -curl https://httpbing.org/post -H "Content-Type: application/x-www-form-urlencoded" \ - --data "key1=value+1&key2=value%3A2" +curl https://httpbin.org/post -d 'shipment[to_address][id]=adr_HrBKVA85' -d 'shipment[from_address][id]=adr_VtuTOj7o' -d 'shipment[parcel][id]=prcl_WDv2VzHp' -d 'shipment[is_return]=true' -d 'shipment[customs_info][id]=cstinfo_bl5sE20Y' +curl https://httpbing.org/post -H "Content-Type: application/x-www-form-urlencoded" --data "key1=value+1&key2=value%3A2" diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index ac6617c9..203896f5 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -2,10 +2,13 @@ package cmd import ( "fmt" + "io/ioutil" + "path/filepath" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/myexec" "github.com/httprunner/httprunner/v4/hrp/internal/version" "github.com/httprunner/httprunner/v4/hrp/pkg/convert" @@ -55,12 +58,35 @@ var convertCmd = &cobra.Command{ log.Info().Str("outputType", outputType.String()).Msg("set default") } + var files []string for _, arg := range args { - if err := caseConverter.Convert(arg, fromType, outputType); err != nil { - log.Error().Err(err).Str("path", arg). + if builtin.IsFolderPathExists(arg) { + fs, err := ioutil.ReadDir(arg) + if err != nil { + log.Error().Err(err).Str("path", arg).Msg("read dir failed") + continue + } + for _, f := range fs { + files = append(files, filepath.Join(arg, f.Name())) + } + } else { + files = append(files, arg) + } + } + + for _, file := range files { + extName := filepath.Ext(file) + if !builtin.Contains(fromType.Extensions(), extName) { + log.Warn().Str("path", file). + Strs("expectExtensions", fromType.Extensions()). + Msg("skip file") + continue + } + + if err := caseConverter.Convert(file, fromType, outputType); err != nil { + log.Error().Err(err).Str("path", file). Str("outputType", outputType.String()). Msg("convert case failed") - return err } } diff --git a/hrp/pkg/convert/from_postman.go b/hrp/pkg/convert/from_postman.go index 99774bea..8518789b 100644 --- a/hrp/pkg/convert/from_postman.go +++ b/hrp/pkg/convert/from_postman.go @@ -330,17 +330,29 @@ func (s *stepFromPostman) makeRequestBodyRaw(item *TItem) (err error) { } }() + languageType := "text" + iOptions := item.Request.Body.Options + if iOptions != nil { + iLanguage := iOptions.(map[string]interface{})["raw"] + if iLanguage != nil { + languageType = iLanguage.(map[string]interface{})["language"].(string) + } + } + s.Request.Body = item.Request.Body.Raw contentType := s.Request.Headers["Content-Type"] - if strings.Contains(contentType, "application/json") { + if strings.Contains(contentType, "application/json") || languageType == "json" { var iBody interface{} err = json.Unmarshal([]byte(item.Request.Body.Raw), &iBody) if err != nil { - log.Warn().Err(err).Msg("33333") return errors.Wrap(err, "make request body (raw -> json) failed") } s.Request.Body = iBody } + + if contentType == "" { + s.Request.Headers["Content-Type"] = contentTypeMap[languageType] + } return } diff --git a/hrp/pkg/convert/main.go b/hrp/pkg/convert/main.go index 602489fe..7ac5a238 100644 --- a/hrp/pkg/convert/main.go +++ b/hrp/pkg/convert/main.go @@ -18,6 +18,7 @@ const ( suffixYAML = ".yaml" suffixGoTest = ".go" suffixPyTest = ".py" + suffixHAR = ".har" ) type FromType int @@ -54,6 +55,25 @@ func (fromType FromType) String() string { } } +func (fromType FromType) Extensions() []string { + switch fromType { + case FromTypeYAML: + return []string{suffixYAML, ".yml"} + case FromTypeHAR: + return []string{suffixHAR} + case FromTypePostman, FromTypeSwagger: + return []string{suffixJSON} + case FromTypeCurl: + return []string{".txt", ".curl"} + case FromTypeGotest: + return []string{suffixGoTest} + case FromTypePyest: + return []string{suffixPyTest} + default: + return []string{suffixJSON} + } +} + type OutputType int const (