mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-05 07:49:37 +08:00
Merge branch 'master' into auto-reset-session
This commit is contained in:
7
.github/workflows/smoketest.yml
vendored
7
.github/workflows/smoketest.yml
vendored
@@ -16,12 +16,17 @@ jobs:
|
|||||||
|
|
||||||
name: smoketest - httprunner - ${{ matrix.python-version }} on ${{ matrix.os }}
|
name: smoketest - httprunner - ${{ matrix.python-version }} on ${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
services:
|
||||||
|
service-httpbin:
|
||||||
|
image: kennethreitz/httpbin
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 6
|
max-parallel: 6
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.7', '3.8', '3.9', '3.10']
|
python-version: ['3.7', '3.8', '3.9', '3.10']
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest] # FIXME: docker services are not supported on macos-latest, windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
11
.github/workflows/unittest.yml
vendored
11
.github/workflows/unittest.yml
vendored
@@ -14,12 +14,17 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
py-httprunner:
|
py-httprunner:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
services:
|
||||||
|
service-httpbin:
|
||||||
|
image: kennethreitz/httpbin
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 12
|
max-parallel: 12
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.7', '3.8', '3.9', '3.10']
|
python-version: ['3.7', '3.8', '3.9', '3.10']
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest] # FIXME: docker services are not supported on macos-latest, windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -45,7 +50,7 @@ jobs:
|
|||||||
poetry run coverage xml
|
poetry run coverage xml
|
||||||
poetry run coverage report -m
|
poetry run coverage report -m
|
||||||
- name: Codecov
|
- name: Codecov
|
||||||
uses: codecov/codecov-action@v1.0.5
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
# User defined upload name. Visible in Codecov UI
|
# User defined upload name. Visible in Codecov UI
|
||||||
name: httprunner
|
name: httprunner
|
||||||
@@ -78,7 +83,7 @@ jobs:
|
|||||||
- name: Run coverage
|
- name: Run coverage
|
||||||
run: go test -coverprofile="cover.out" -covermode=atomic -race ./...
|
run: go test -coverprofile="cover.out" -covermode=atomic -race ./...
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
name: hrp (HttpRunner+) # User defined upload name. Visible in Codecov UI
|
name: hrp (HttpRunner+) # User defined upload name. Visible in Codecov UI
|
||||||
token: ${{ secrets.CODECOV_TOKEN }} # Repository upload token
|
token: ${{ secrets.CODECOV_TOKEN }} # Repository upload token
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
# Release History
|
# Release History
|
||||||
|
|
||||||
## v4.3.5 (2023-07-20)
|
## v4.3.5 (2023-07-23)
|
||||||
|
|
||||||
|
- refactor: send events to Google Analytics 4, replace GA v1
|
||||||
|
- fix: failure unittests caused by httpbin.org, replace with docker service
|
||||||
|
- fix: handle unstable unittests, restore github actions pipeline
|
||||||
|
- feat: support to reset driver automatically when uia2 crashed
|
||||||
|
- feat: support to reset session when wda request failed
|
||||||
|
|
||||||
**go version**
|
**go version**
|
||||||
|
|
||||||
- feat: support to reset driver automatically when uia2 crashed
|
- feat: report GA4 events for hrp cmd
|
||||||
- feat: support to reset session when wda request failed
|
- change: create python venv with httprunner minimum version v4.3.5
|
||||||
|
- fix #1603: ensure path suffix '/' exists
|
||||||
|
|
||||||
|
**python version**
|
||||||
|
|
||||||
|
- fix: upgrade pyyaml from 5.4.1 to 6.0.1, fix installing error
|
||||||
|
- refactor: update httprunner dependencies
|
||||||
|
|
||||||
## v4.3.4 (2023-06-01)
|
## v4.3.4 (2023-06-01)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: a-b.c/1.yml
|
# FROM: a-b.c/1.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: a-b.c/2 3.yml
|
# FROM: a-b.c/2 3.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
from httprunner import RunTestCase
|
from httprunner import RunTestCase
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-empty-project",
|
"project_name": "demo-empty-project",
|
||||||
"create_time": "2023-05-31T20:46:10.736189+08:00",
|
"create_time": "2023-07-23T13:54:23.516072+08:00",
|
||||||
"hrp_version": "v4.3.4"
|
"hrp_version": "v4.3.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-with-go-plugin",
|
"project_name": "demo-with-go-plugin",
|
||||||
"create_time": "2023-05-31T20:44:56.120736+08:00",
|
"create_time": "2023-07-23T14:30:10.985053+08:00",
|
||||||
"hrp_version": "v4.3.4"
|
"hrp_version": "v4.3.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-with-py-plugin",
|
"project_name": "demo-with-py-plugin",
|
||||||
"create_time": "2023-05-31T20:45:00.44921+08:00",
|
"create_time": "2023-07-23T14:30:18.556239+08:00",
|
||||||
"hrp_version": "v4.3.4"
|
"hrp_version": "v4.3.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-without-plugin",
|
"project_name": "demo-without-plugin",
|
||||||
"create_time": "2023-05-31T20:46:10.621009+08:00",
|
"create_time": "2023-07-23T13:54:23.368356+08:00",
|
||||||
"hrp_version": "v4.3.4"
|
"hrp_version": "v4.3.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
config:
|
config:
|
||||||
name: basic test with httpbin
|
name: basic test with httpbin
|
||||||
base_url: https://httpbin.org/
|
base_url: ${get_httpbin_server()}
|
||||||
|
|
||||||
teststeps:
|
teststeps:
|
||||||
-
|
-
|
||||||
@@ -10,7 +10,7 @@ teststeps:
|
|||||||
method: GET
|
method: GET
|
||||||
validate:
|
validate:
|
||||||
- eq: ["status_code", 200]
|
- eq: ["status_code", 200]
|
||||||
- eq: [body.headers.Host, "httpbin.org"]
|
- eq: [body.headers.Host, "127.0.0.1"]
|
||||||
|
|
||||||
-
|
-
|
||||||
name: user-agent
|
name: user-agent
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.1.4
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: basic.yml
|
# FROM: basic.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|
||||||
class TestCaseBasic(HttpRunner):
|
class TestCaseBasic(HttpRunner):
|
||||||
|
|
||||||
config = Config("basic test with httpbin").base_url("https://httpbin.org/")
|
config = Config("basic test with httpbin").base_url("${get_httpbin_server()}")
|
||||||
|
|
||||||
teststeps = [
|
teststeps = [
|
||||||
Step(
|
Step(
|
||||||
@@ -13,7 +13,7 @@ class TestCaseBasic(HttpRunner):
|
|||||||
.get("/headers")
|
.get("/headers")
|
||||||
.validate()
|
.validate()
|
||||||
.assert_equal("status_code", 200)
|
.assert_equal("status_code", 200)
|
||||||
.assert_equal("body.headers.Host", "httpbin.org")
|
.assert_equal("body.headers.Host", "127.0.0.1")
|
||||||
),
|
),
|
||||||
Step(
|
Step(
|
||||||
RunRequest("user-agent")
|
RunRequest("user-agent")
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import uuid
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from httprunner.utils import HTTP_BIN_URL
|
||||||
|
|
||||||
|
|
||||||
def get_httpbin_server():
|
def get_httpbin_server():
|
||||||
return "https://httpbin.org"
|
return HTTP_BIN_URL
|
||||||
|
|
||||||
|
|
||||||
def setup_testcase(variables):
|
def setup_testcase(variables):
|
||||||
@@ -17,7 +19,7 @@ def setup_testcase(variables):
|
|||||||
|
|
||||||
|
|
||||||
def teardown_testcase():
|
def teardown_testcase():
|
||||||
logger.info(f"teardown_testcase.")
|
logger.info("teardown_testcase.")
|
||||||
|
|
||||||
|
|
||||||
def setup_teststep(request, variables):
|
def setup_teststep(request, variables):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.1.4
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: hooks.yml
|
# FROM: hooks.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.1.4
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: load_image.yml
|
# FROM: load_image.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.1.4
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: upload.yml
|
# FROM: upload.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
config:
|
config:
|
||||||
name: basic test with httpbin
|
name: basic test with httpbin
|
||||||
base_url: https://httpbin.org/
|
base_url: ${get_httpbin_server()}
|
||||||
|
|
||||||
teststeps:
|
teststeps:
|
||||||
-
|
-
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.1.4
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: validate.yml
|
# FROM: validate.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|
||||||
class TestCaseValidate(HttpRunner):
|
class TestCaseValidate(HttpRunner):
|
||||||
|
|
||||||
config = Config("basic test with httpbin").base_url("https://httpbin.org/")
|
config = Config("basic test with httpbin").base_url("${get_httpbin_server()}")
|
||||||
|
|
||||||
teststeps = [
|
teststeps = [
|
||||||
Step(
|
Step(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.1.4
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: cookie_manipulation/hardcode.yml
|
# FROM: cookie_manipulation/hardcode.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.1.4
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: cookie_manipulation/set_delete_cookies.yml
|
# FROM: cookie_manipulation/set_delete_cookies.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: request_methods/hardcode.yml
|
# FROM: request_methods/hardcode.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: request_methods/request_with_functions.yml
|
# FROM: request_methods/request_with_functions.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: request_methods/request_with_parameters.yml
|
# FROM: request_methods/request_with_parameters.yml
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: request_methods/request_with_testcase_reference.yml
|
# FROM: request_methods/request_with_testcase_reference.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
from httprunner import RunTestCase
|
from httprunner import RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: request_methods/request_with_variables.yml
|
# FROM: request_methods/request_with_variables.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: request_methods/validate_with_functions.yml
|
# FROM: request_methods/validate_with_functions.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v4.3.0
|
# NOTE: Generated By HttpRunner v4.3.5
|
||||||
# FROM: request_methods/validate_with_variables.yml
|
# FROM: request_methods/validate_with_variables.yml
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -18,7 +18,6 @@ require (
|
|||||||
github.com/maja42/goval v1.2.1
|
github.com/maja42/goval v1.2.1
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/otiai10/gosseract/v2 v2.4.0
|
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.13.0
|
github.com/prometheus/client_golang v1.13.0
|
||||||
github.com/rs/zerolog v1.29.1
|
github.com/rs/zerolog v1.29.1
|
||||||
@@ -53,6 +52,7 @@ require (
|
|||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||||
@@ -67,6 +67,7 @@ require (
|
|||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||||
|
|||||||
15
go.sum
15
go.sum
@@ -203,7 +203,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
|
|||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -249,15 +250,9 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
|||||||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
|
||||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
|
||||||
github.com/otiai10/gosseract/v2 v2.4.0 h1:gYd3mx6FuMtIlxL4sYb9JLCFEDzg09VgNSZRNbqpiGM=
|
|
||||||
github.com/otiai10/gosseract/v2 v2.4.0/go.mod h1:fhbIDRh29bj13vni6RT3gtWKjKCAeqDYI4C1dxeJuek=
|
|
||||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
|
||||||
github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
|
|
||||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -292,7 +287,9 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua
|
|||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
||||||
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
||||||
|
|||||||
@@ -93,17 +93,14 @@ func (b *HRPBoomer) SetPython3Venv(venv string) *HRPBoomer {
|
|||||||
|
|
||||||
// Run starts to run load test for one or multiple testcases.
|
// Run starts to run load test for one or multiple testcases.
|
||||||
func (b *HRPBoomer) Run(testcases ...ITestCase) {
|
func (b *HRPBoomer) Run(testcases ...ITestCase) {
|
||||||
event := sdk.EventTracking{
|
startTime := time.Now()
|
||||||
Category: "RunLoadTests",
|
|
||||||
Action: "hrp boom",
|
|
||||||
}
|
|
||||||
// report start event
|
|
||||||
go sdk.SendEvent(event)
|
|
||||||
// report execution timing event
|
|
||||||
defer sdk.SendEvent(event.StartTiming("execution"))
|
|
||||||
|
|
||||||
// quit all plugins
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
// report boom event
|
||||||
|
sdk.SendGA4Event("hrp_boomer_run", map[string]interface{}{
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// quit all plugins
|
||||||
pluginMap.Range(func(key, value interface{}) bool {
|
pluginMap.Range(func(key, value interface{}) bool {
|
||||||
if plugin, ok := value.(funplugin.IPlugin); ok {
|
if plugin, ok := value.(funplugin.IPlugin); ok {
|
||||||
plugin.Quit()
|
plugin.Quit()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func TestBoomerStandaloneRun(t *testing.T) {
|
|||||||
defer removeHashicorpGoPlugin()
|
defer removeHashicorpGoPlugin()
|
||||||
|
|
||||||
testcase1 := &TestCase{
|
testcase1 := &TestCase{
|
||||||
Config: NewConfig("TestCase1").SetBaseURL("https://httpbin.org"),
|
Config: NewConfig("TestCase1").SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []IStep{
|
||||||
NewStep("headers").
|
NewStep("headers").
|
||||||
GET("/headers").
|
GET("/headers").
|
||||||
|
|||||||
13
hrp/build.go
13
hrp/build.go
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -172,6 +173,12 @@ func (pt *pluginTemplate) generateGo(output string) error {
|
|||||||
// buildGo builds debugtalk.go to debugtalk.bin
|
// buildGo builds debugtalk.go to debugtalk.bin
|
||||||
func buildGo(path string, output string) error {
|
func buildGo(path string, output string) error {
|
||||||
log.Info().Str("path", path).Str("output", output).Msg("start to build go plugin")
|
log.Info().Str("path", path).Str("output", output).Msg("start to build go plugin")
|
||||||
|
|
||||||
|
// report GA event
|
||||||
|
sdk.SendGA4Event("hrp_build_plugin", map[string]interface{}{
|
||||||
|
"pluginType": "go",
|
||||||
|
})
|
||||||
|
|
||||||
content, err := os.ReadFile(path)
|
content, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("failed to read file")
|
log.Error().Err(err).Msg("failed to read file")
|
||||||
@@ -197,6 +204,12 @@ func buildGo(path string, output string) error {
|
|||||||
// buildPy completes funppy information in debugtalk.py
|
// buildPy completes funppy information in debugtalk.py
|
||||||
func buildPy(path string, output string) error {
|
func buildPy(path string, output string) error {
|
||||||
log.Info().Str("path", path).Str("output", output).Msg("start to prepare python plugin")
|
log.Info().Str("path", path).Str("output", output).Msg("start to prepare python plugin")
|
||||||
|
|
||||||
|
// report GA event
|
||||||
|
sdk.SendGA4Event("hrp_build_plugin", map[string]interface{}{
|
||||||
|
"pluginType": "python",
|
||||||
|
})
|
||||||
|
|
||||||
// check the syntax of debugtalk.py
|
// check the syntax of debugtalk.py
|
||||||
err := myexec.ExecPython3Command("py_compile", path)
|
err := myexec.ExecPython3Command("py_compile", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,7 +21,16 @@ func format(data map[string]string) string {
|
|||||||
var listAndroidDevicesCmd = &cobra.Command{
|
var listAndroidDevicesCmd = &cobra.Command{
|
||||||
Use: "devices",
|
Use: "devices",
|
||||||
Short: "List all Android devices",
|
Short: "List all Android devices",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_adb_devices", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
deviceList, err := uixt.GetAndroidDevices(serial)
|
deviceList, err := uixt.GetAndroidDevices(serial)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|||||||
@@ -3,16 +3,28 @@ package adb
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
var screencapAndroidDevicesCmd = &cobra.Command{
|
var screencapAndroidDevicesCmd = &cobra.Command{
|
||||||
Use: "screencap",
|
Use: "screencap",
|
||||||
Short: "Start android screen capture",
|
Short: "Start android screen capture",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_adb_screencap", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
device, err := getDevice(serial)
|
device, err := getDevice(serial)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp"
|
"github.com/httprunner/httprunner/v4/hrp"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/boomer"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/boomer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +30,16 @@ var boomCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
setLogLevel(logLevel)
|
setLogLevel(logLevel)
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_boom", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
var paths []hrp.ITestCase
|
var paths []hrp.ITestCase
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
path := hrp.TestCasePath(arg)
|
path := hrp.TestCasePath(arg)
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp"
|
"github.com/httprunner/httprunner/v4/hrp"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildCmd = &cobra.Command{
|
var buildCmd = &cobra.Command{
|
||||||
@@ -16,7 +20,15 @@ var buildCmd = &cobra.Command{
|
|||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
setLogLevel(logLevel)
|
setLogLevel(logLevel)
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_build", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
return hrp.BuildPlugin(args[0], output)
|
return hrp.BuildPlugin(args[0], output)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package ios
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +22,16 @@ var listAppsCmd = &cobra.Command{
|
|||||||
Use: "apps",
|
Use: "apps",
|
||||||
Short: "List all iOS installed apps",
|
Short: "List all iOS installed apps",
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_apps", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
device, err := getDevice(udid)
|
device, err := getDevice(udid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
@@ -69,7 +72,16 @@ var listDevicesCmd = &cobra.Command{
|
|||||||
Use: "devices",
|
Use: "devices",
|
||||||
Short: "List all iOS devices",
|
Short: "List all iOS devices",
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_devices", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
devices, err := uixt.GetIOSDevices(udid)
|
devices, err := uixt.GetIOSDevices(udid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|||||||
@@ -5,18 +5,29 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mountCmd represents the mount command
|
// mountCmd represents the mount command
|
||||||
var mountCmd = &cobra.Command{
|
var mountCmd = &cobra.Command{
|
||||||
Use: "mount",
|
Use: "mount",
|
||||||
Short: "A brief description of your command",
|
Short: "A brief description of your command",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_mount", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
device, err := getDevice(udid)
|
device, err := getDevice(udid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ios
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,13 +12,23 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var pcapCmd = &cobra.Command{
|
var pcapCmd = &cobra.Command{
|
||||||
Use: "pcap",
|
Use: "pcap",
|
||||||
Short: "capture ios network packets",
|
Short: "capture ios network packets",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_pcap", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
pcapOptions := []uixt.IOSPcapOption{}
|
pcapOptions := []uixt.IOSPcapOption{}
|
||||||
if pid > 0 {
|
if pid > 0 {
|
||||||
pcapOptions = append(pcapOptions, uixt.WithIOSPcapPID(pid))
|
pcapOptions = append(pcapOptions, uixt.WithIOSPcapPID(pid))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ios
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,13 +12,23 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var perfCmd = &cobra.Command{
|
var perfCmd = &cobra.Command{
|
||||||
Use: "perf",
|
Use: "perf",
|
||||||
Short: "capture ios performance data (cpu,mem,disk,net,fps,etc.)",
|
Short: "capture ios performance data (cpu,mem,disk,net,fps,etc.)",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_perf", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
perfOptions := []uixt.IOSPerfOption{}
|
perfOptions := []uixt.IOSPerfOption{}
|
||||||
for _, p := range indicators {
|
for _, p := range indicators {
|
||||||
switch p {
|
switch p {
|
||||||
|
|||||||
@@ -2,17 +2,29 @@ package ios
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
var psCmd = &cobra.Command{
|
var psCmd = &cobra.Command{
|
||||||
Use: "ps",
|
Use: "ps",
|
||||||
Short: "show running processes",
|
Short: "show running processes",
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_ps", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
device, err := getDevice(udid)
|
device, err := getDevice(udid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -2,15 +2,28 @@ package ios
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rebootCmd = &cobra.Command{
|
var rebootCmd = &cobra.Command{
|
||||||
Use: "reboot",
|
Use: "reboot",
|
||||||
Short: "reboot or shutdown ios device",
|
Short: "reboot or shutdown ios device",
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_reboot", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
device, err := getDevice(udid)
|
device, err := getDevice(udid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,17 +4,30 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
var xctestCmd = &cobra.Command{
|
var xctestCmd = &cobra.Command{
|
||||||
Use: "xctest",
|
Use: "xctest",
|
||||||
Short: "run xctest",
|
Short: "run xctest",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_xctest", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
if bundleID == "" {
|
if bundleID == "" {
|
||||||
return fmt.Errorf("bundleID is required")
|
return fmt.Errorf("bundleID is required")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/pytest"
|
"github.com/httprunner/httprunner/v4/hrp/internal/pytest"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,11 +22,20 @@ var pytestCmd = &cobra.Command{
|
|||||||
setLogLevel(logLevel)
|
setLogLevel(logLevel)
|
||||||
},
|
},
|
||||||
DisableFlagParsing: true, // allow to pass any args to pytest
|
DisableFlagParsing: true, // allow to pass any args to pytest
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_pytest", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
packages := []string{
|
packages := []string{
|
||||||
fmt.Sprintf("httprunner==%s", version.HttpRunnerMinimumVersion),
|
fmt.Sprintf("httprunner==%s", version.HttpRunnerMinimumVersion),
|
||||||
}
|
}
|
||||||
_, err := myexec.EnsurePython3Venv(venv, packages...)
|
_, err = myexec.EnsurePython3Venv(venv, packages...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("python3 venv is not ready")
|
log.Error().Err(err).Msg("python3 venv is not ready")
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/wiki"
|
"github.com/httprunner/httprunner/v4/hrp/internal/wiki"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,7 +17,15 @@ var wikiCmd = &cobra.Command{
|
|||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
setLogLevel(logLevel)
|
setLogLevel(logLevel)
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_wiki", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
return wiki.OpenWiki()
|
return wiki.OpenWiki()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,23 +86,29 @@ func EndsWith(t assert.TestingT, actual, expected interface{}, msgAndArgs ...int
|
|||||||
func EqualLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
func EqualLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
||||||
length, err := convertInt(expected)
|
length, err := convertInt(expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("expect int type, got %#v", expected), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
ok, l := getLen(actual)
|
||||||
return assert.Len(t, actual, length, msgAndArgs...)
|
if !ok {
|
||||||
|
return assert.Fail(t, fmt.Sprintf("actual value %v(%T) can't get length", actual, actual), msgAndArgs...)
|
||||||
|
}
|
||||||
|
if l != length {
|
||||||
|
return assert.Fail(t, fmt.Sprintf("%v length == %d, expect == %d", actual, l, length), msgAndArgs...)
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func GreaterThanLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
func GreaterThanLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
||||||
length, err := convertInt(expected)
|
length, err := convertInt(expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("expect int type, got %#v", expected), msgAndArgs...)
|
||||||
}
|
}
|
||||||
ok, l := getLen(actual)
|
ok, l := getLen(actual)
|
||||||
if !ok {
|
if !ok {
|
||||||
return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("actual value %v(%T) can't get length", actual, actual), msgAndArgs...)
|
||||||
}
|
}
|
||||||
if l <= length {
|
if l <= length {
|
||||||
return assert.Fail(t, fmt.Sprintf("\"%s\" should be more than %d item(s), but has %d", actual, length, l), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("%v length == %d, expect > %d", actual, l, length), msgAndArgs...)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -110,14 +116,14 @@ func GreaterThanLength(t assert.TestingT, actual, expected interface{}, msgAndAr
|
|||||||
func GreaterOrEqualsLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
func GreaterOrEqualsLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
||||||
length, err := convertInt(expected)
|
length, err := convertInt(expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("expect int type, got %#v", expected), msgAndArgs...)
|
||||||
}
|
}
|
||||||
ok, l := getLen(actual)
|
ok, l := getLen(actual)
|
||||||
if !ok {
|
if !ok {
|
||||||
return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("actual value %v(%T) can't get length", actual, actual), msgAndArgs...)
|
||||||
}
|
}
|
||||||
if l < length {
|
if l < length {
|
||||||
return assert.Fail(t, fmt.Sprintf("\"%s\" should be no less than %d item(s), but has %d", actual, length, l), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("%v length == %d, expect >= %d", actual, l, length), msgAndArgs...)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -125,14 +131,14 @@ func GreaterOrEqualsLength(t assert.TestingT, actual, expected interface{}, msgA
|
|||||||
func LessThanLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
func LessThanLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
||||||
length, err := convertInt(expected)
|
length, err := convertInt(expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("expect int type, got %#v", expected), msgAndArgs...)
|
||||||
}
|
}
|
||||||
ok, l := getLen(actual)
|
ok, l := getLen(actual)
|
||||||
if !ok {
|
if !ok {
|
||||||
return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("actual value %v(%T) can't get length", actual, actual), msgAndArgs...)
|
||||||
}
|
}
|
||||||
if l >= length {
|
if l >= length {
|
||||||
return assert.Fail(t, fmt.Sprintf("\"%s\" should be less than %d item(s), but has %d", actual, length, l), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("%v length == %d, expect < %d", actual, l, length), msgAndArgs...)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -140,14 +146,14 @@ func LessThanLength(t assert.TestingT, actual, expected interface{}, msgAndArgs
|
|||||||
func LessOrEqualsLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
func LessOrEqualsLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
||||||
length, err := convertInt(expected)
|
length, err := convertInt(expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("expect int type, got %#v", expected), msgAndArgs...)
|
||||||
}
|
}
|
||||||
ok, l := getLen(actual)
|
ok, l := getLen(actual)
|
||||||
if !ok {
|
if !ok {
|
||||||
return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("actual value %v(%T) can't get length", actual, actual), msgAndArgs...)
|
||||||
}
|
}
|
||||||
if l > length {
|
if l > length {
|
||||||
return assert.Fail(t, fmt.Sprintf("\"%s\" should be no more than %d item(s), but has %d", actual, length, l), msgAndArgs...)
|
return assert.Fail(t, fmt.Sprintf("%v length == %d, expect <= %d", actual, l, length), msgAndArgs...)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,9 @@ package pytest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunPytest(args []string) error {
|
func RunPytest(args []string) error {
|
||||||
sdk.SendEvent(sdk.EventTracking{
|
|
||||||
Category: "RunAPITests",
|
|
||||||
Action: "hrp pytest",
|
|
||||||
})
|
|
||||||
|
|
||||||
args = append([]string{"run"}, args...)
|
args = append([]string{"run"}, args...)
|
||||||
return myexec.ExecPython3Command("httprunner", args...)
|
return myexec.ExecPython3Command("httprunner", args...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ func TestGenDemoExamples(t *testing.T) {
|
|||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
dir = "../../../examples/demo-with-py-plugin"
|
dir = "../../../examples/demo-with-py-plugin"
|
||||||
venv := filepath.Join(dir, ".venv")
|
venv := filepath.Join(dir, ".venv")
|
||||||
err = CreateScaffold(dir, Py, venv, true)
|
err = CreateScaffold(dir, Py, venv, true)
|
||||||
|
|||||||
@@ -54,11 +54,15 @@ func CopyFile(templateFile, targetFile string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateScaffold(projectName string, pluginType PluginType, venv string, force bool) error {
|
func CreateScaffold(projectName string, pluginType PluginType, venv string, force bool) error {
|
||||||
// report event
|
// report GA event
|
||||||
sdk.SendEvent(sdk.EventTracking{
|
startTime := time.Now()
|
||||||
Category: "Scaffold",
|
defer func() {
|
||||||
Action: "hrp startproject",
|
sdk.SendGA4Event("hrp_startproject", map[string]interface{}{
|
||||||
})
|
"pluginType": string(pluginType),
|
||||||
|
"force": force,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("projectName", projectName).
|
Str("projectName", projectName).
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
{
|
{
|
||||||
"check": "body.url",
|
"check": "body.url",
|
||||||
"assert": "equals",
|
"assert": "equals",
|
||||||
"expect": "https://postman-echo.com/post",
|
"expect": "https://postman-echo.com/post/",
|
||||||
"msg": "assert response body url"
|
"msg": "assert response body url"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -26,5 +26,5 @@ validate:
|
|||||||
msg: assert response body json
|
msg: assert response body json
|
||||||
- check: body.url
|
- check: body.url
|
||||||
assert: equals
|
assert: equals
|
||||||
expect: https://postman-echo.com/post
|
expect: https://postman-echo.com/post/
|
||||||
msg: assert response body url
|
msg: assert response body url
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
{
|
{
|
||||||
"check": "body.url",
|
"check": "body.url",
|
||||||
"assert": "equals",
|
"assert": "equals",
|
||||||
"expect": "https://postman-echo.com/put",
|
"expect": "https://postman-echo.com/put/",
|
||||||
"msg": "assert response body url"
|
"msg": "assert response body url"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -26,5 +26,5 @@ validate:
|
|||||||
msg: assert response body json
|
msg: assert response body json
|
||||||
- check: body.url
|
- check: body.url
|
||||||
assert: equals
|
assert: equals
|
||||||
expect: https://postman-echo.com/put
|
expect: https://postman-echo.com/put/
|
||||||
msg: assert response body url
|
msg: assert response body url
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// NOTE: Generated By hrp v4.3.4, DO NOT EDIT!
|
// NOTE: Generated By hrp v4.3.5, DO NOT EDIT!
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
package sdk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
gaAPIDebugURL = "https://www.google-analytics.com/debug/collect" // used for debug
|
|
||||||
gaAPIURL = "https://www.google-analytics.com/collect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GAClient struct {
|
|
||||||
TrackingID string `form:"tid"` // Tracking ID / Property ID, XX-XXXXXXX-X
|
|
||||||
ClientID string `form:"cid"` // Anonymous Client ID
|
|
||||||
Version string `form:"v"` // Version
|
|
||||||
httpClient *http.Client // http client session
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGAClient creates a new GAClient object with the trackingID and clientID.
|
|
||||||
func NewGAClient(trackingID, clientID string) *GAClient {
|
|
||||||
return &GAClient{
|
|
||||||
TrackingID: trackingID,
|
|
||||||
ClientID: clientID,
|
|
||||||
Version: "1", // constant v1
|
|
||||||
httpClient: &http.Client{
|
|
||||||
Timeout: 5 * time.Second,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendEvent sends one event to Google Analytics
|
|
||||||
func (g *GAClient) SendEvent(e IEvent) error {
|
|
||||||
var data url.Values
|
|
||||||
if event, ok := e.(UserTimingTracking); ok {
|
|
||||||
event.duration = time.Since(event.startTime)
|
|
||||||
data = event.ToUrlValues()
|
|
||||||
} else {
|
|
||||||
data = e.ToUrlValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
// append common params
|
|
||||||
data.Add("v", g.Version)
|
|
||||||
data.Add("tid", g.TrackingID)
|
|
||||||
data.Add("cid", g.ClientID)
|
|
||||||
|
|
||||||
resp, err := g.httpClient.PostForm(gaAPIURL, data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("response status: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func structToUrlValues(i interface{}) (values url.Values) {
|
|
||||||
values = url.Values{}
|
|
||||||
iVal := reflect.ValueOf(i)
|
|
||||||
for i := 0; i < iVal.NumField(); i++ {
|
|
||||||
formTagName := iVal.Type().Field(i).Tag.Get("form")
|
|
||||||
if formTagName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iVal.Field(i).IsZero() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
values.Set(formTagName, fmt.Sprint(iVal.Field(i)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package sdk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSendEvents(t *testing.T) {
|
|
||||||
event := EventTracking{
|
|
||||||
Category: "unittest",
|
|
||||||
Action: "SendEvents",
|
|
||||||
Value: 123,
|
|
||||||
}
|
|
||||||
err := SendEvent(event)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStructToUrlValues(t *testing.T) {
|
|
||||||
event := EventTracking{
|
|
||||||
Category: "unittest",
|
|
||||||
Action: "convert",
|
|
||||||
Label: "v0.3.0",
|
|
||||||
Value: 123,
|
|
||||||
}
|
|
||||||
val := structToUrlValues(event)
|
|
||||||
if val.Encode() != "ea=convert&ec=unittest&el=v0.3.0&ev=123" {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package sdk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IEvent interface {
|
|
||||||
ToUrlValues() url.Values
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventTracking struct {
|
|
||||||
HitType string `form:"t"` // Event hit type = event
|
|
||||||
Category string `form:"ec"` // Required. Event Category.
|
|
||||||
Action string `form:"ea"` // Required. Event Action.
|
|
||||||
Label string `form:"el"` // Optional. Event label, used as version.
|
|
||||||
Value int `form:"ev"` // Optional. Event value, must be non-negative integer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e EventTracking) StartTiming(variable string) UserTimingTracking {
|
|
||||||
return UserTimingTracking{
|
|
||||||
HitType: "timing",
|
|
||||||
Category: e.Category,
|
|
||||||
Variable: variable,
|
|
||||||
Label: e.Label,
|
|
||||||
startTime: time.Now(), // starts the timer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e EventTracking) ToUrlValues() url.Values {
|
|
||||||
e.HitType = "event"
|
|
||||||
e.Label = version.VERSION
|
|
||||||
return structToUrlValues(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserTimingTracking struct {
|
|
||||||
HitType string `form:"t"` // Timing hit type
|
|
||||||
Category string `form:"utc"` // Required. user timing category. e.g. jsonLoader
|
|
||||||
Variable string `form:"utv"` // Required. timing variable. e.g. load
|
|
||||||
Duration string `form:"utt"` // Required. time took duration.
|
|
||||||
Label string `form:"utl"` // Optional. user timing label. e.g jQuery
|
|
||||||
startTime time.Time
|
|
||||||
duration time.Duration // time took duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e UserTimingTracking) ToUrlValues() url.Values {
|
|
||||||
e.HitType = "timing"
|
|
||||||
e.Label = version.VERSION
|
|
||||||
e.Duration = fmt.Sprintf("%d", int64(e.duration.Seconds()*1000))
|
|
||||||
return structToUrlValues(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Exception struct {
|
|
||||||
HitType string `form:"t"` // Hit Type = exception
|
|
||||||
Description string `form:"exd"` // exception description. i.e. IOException
|
|
||||||
IsFatal string `form:"exf"` // if the exception was fatal
|
|
||||||
isFatal bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Exception) ToUrlValues() url.Values {
|
|
||||||
e.HitType = "exception"
|
|
||||||
if e.isFatal {
|
|
||||||
e.IsFatal = "1"
|
|
||||||
} else {
|
|
||||||
e.IsFatal = "0"
|
|
||||||
}
|
|
||||||
return structToUrlValues(e)
|
|
||||||
}
|
|
||||||
211
hrp/internal/sdk/ga4.go
Normal file
211
hrp/internal/sdk/ga4.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/denisbrodbeck/machineid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Measurement Protocol (Google Analytics 4) docs reference:
|
||||||
|
// https://developers.google.com/analytics/devguides/collection/protocol/ga4
|
||||||
|
// debugging tools: https://ga-dev-tools.google/ga4/event-builder/
|
||||||
|
const (
|
||||||
|
ga4APISecret = "w7lKNQIrQsKNS4ikgMPp0Q"
|
||||||
|
ga4MeasurementID = "G-9KHR3VC2LN"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ga4Client *GA4Client
|
||||||
|
userID string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
userID, err = machineid.ProtectedID("hrp")
|
||||||
|
if err != nil {
|
||||||
|
userID = uuid.NewV1().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// init GA4 client
|
||||||
|
ga4Client = NewGA4Client(ga4MeasurementID, ga4APISecret, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GA4Client struct {
|
||||||
|
apiSecret string // Measurement Protocol API secret value
|
||||||
|
measurementID string // MEASUREMENT ID, G-XXXXXXXXXX
|
||||||
|
userID string // A unique identifier for a user
|
||||||
|
httpClient *http.Client // http client session
|
||||||
|
debug bool // send events for validation, used for debug
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGA4Client creates a new GA4Client object with the measurementID and apiSecret.
|
||||||
|
func NewGA4Client(measurementID, apiSecret string, debug ...bool) *GA4Client {
|
||||||
|
dbg := false
|
||||||
|
if len(debug) > 0 {
|
||||||
|
dbg = debug[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &GA4Client{
|
||||||
|
measurementID: measurementID,
|
||||||
|
apiSecret: apiSecret,
|
||||||
|
userID: userID,
|
||||||
|
httpClient: &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
},
|
||||||
|
debug: dbg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
// Required. The name for the event.
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Optional. The parameters for the event.
|
||||||
|
// engagement_time_msec/session_id
|
||||||
|
Params map[string]interface{} `json:"params,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// payload docs reference:
|
||||||
|
// https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag
|
||||||
|
type Payload struct {
|
||||||
|
// Required. Uniquely identifies a user instance of a web client
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
// Optional. A unique identifier for a user
|
||||||
|
UserID string `json:"user_id,omitempty"`
|
||||||
|
// Optional. A Unix timestamp (in microseconds) for the time to associate with the event.
|
||||||
|
// This should only be set to record events that happened in the past.
|
||||||
|
// This value can be overridden via user_property or event timestamps.
|
||||||
|
// Events can be backdated up to 3 calendar days based on the property's timezone.
|
||||||
|
TimestampMicros int64 `json:"timestamp_micros,omitempty"`
|
||||||
|
// Optional. The user properties for the measurement.
|
||||||
|
UserProperties map[string]string `json:"user_properties,omitempty"`
|
||||||
|
// Optional. Set to true to indicate these events should not be used for personalized ads.
|
||||||
|
NonPersonalizedAds bool `json:"non_personalized_ads,omitempty"`
|
||||||
|
// Required. An array of event items. Up to 25 events can be sent per request.
|
||||||
|
Events []Event `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validation docs reference:
|
||||||
|
// https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=gtag
|
||||||
|
type ValidationResponse struct {
|
||||||
|
ValidationMessages []ValidationMessage `json:"validationMessages"` // An array of validation messages.
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidationMessage struct {
|
||||||
|
FieldPath string `json:"fieldPath"` // The path to the field that was invalid.
|
||||||
|
Description string `json:"description"` // A description of the error.
|
||||||
|
ValidationCode ValidationCode `json:"validationCode"` // A ValidationCode that corresponds to the error.
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidationCode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
VALUE_INVALID ValidationCode = "VALUE_INVALID" // The value provided for a fieldPath was invalid.
|
||||||
|
VALUE_REQUIRED ValidationCode = "VALUE_REQUIRED" // A required value for a fieldPath was not provided.
|
||||||
|
NAME_INVALID ValidationCode = "NAME_INVALID" // The name provided was invalid.
|
||||||
|
NAME_RESERVED ValidationCode = "NAME_RESERVED" // The name provided was one of the reserved names.
|
||||||
|
VALUE_OUT_OF_BOUNDS ValidationCode = "VALUE_OUT_OF_BOUNDS" // The value provided was too large.
|
||||||
|
EXCEEDED_MAX_ENTITIES ValidationCode = "EXCEEDED_MAX_ENTITIES" // There were too many parameters in the request.
|
||||||
|
NAME_DUPLICATED ValidationCode = "NAME_DUPLICATED" // The same name was provided more than once in the request.
|
||||||
|
)
|
||||||
|
|
||||||
|
// SendEvent sends one event to Google Analytics
|
||||||
|
func (g *GA4Client) SendEvent(event Event) error {
|
||||||
|
query := url.Values{}
|
||||||
|
query.Add("api_secret", g.apiSecret)
|
||||||
|
query.Add("measurement_id", g.measurementID)
|
||||||
|
|
||||||
|
var uri string
|
||||||
|
if g.debug {
|
||||||
|
uri = fmt.Sprintf("https://www.google-analytics.com/debug/mp/collect?%s", query.Encode())
|
||||||
|
} else {
|
||||||
|
uri = fmt.Sprintf("https://www.google-analytics.com/mp/collect?%s", query.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append event params
|
||||||
|
if event.Params == nil {
|
||||||
|
event.Params = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
event.Params["os"] = runtime.GOOS
|
||||||
|
event.Params["arch"] = runtime.GOARCH
|
||||||
|
event.Params["go_version"] = runtime.Version()
|
||||||
|
event.Params["hrp_version"] = version.VERSION
|
||||||
|
|
||||||
|
payload := Payload{
|
||||||
|
ClientID: fmt.Sprintf("%d.%d", rand.Int31(), time.Now().Unix()),
|
||||||
|
UserID: g.userID,
|
||||||
|
TimestampMicros: time.Now().UnixMicro(),
|
||||||
|
Events: []Event{event},
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := json.Marshal(payload)
|
||||||
|
if g.debug {
|
||||||
|
log.Debug().
|
||||||
|
Str("uri", uri).
|
||||||
|
Interface("payload", payload).
|
||||||
|
Msg("send GA4 event")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "marshal GA4 request payload failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
body := bytes.NewReader(bs)
|
||||||
|
res, err := g.httpClient.Post(uri, "application/json", body)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "request GA4 failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode >= 300 {
|
||||||
|
return fmt.Errorf("validation response got unexpected status %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !g.debug {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err = ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "read GA4 response body failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
validationResponse := ValidationResponse{}
|
||||||
|
err = json.Unmarshal(bs, &validationResponse)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unmarshal GA4 response body failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Int("statusCode", res.StatusCode).
|
||||||
|
Interface("validationResponse", validationResponse).
|
||||||
|
Msg("get GA4 validation response")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendGA4Event(name string, params map[string]interface{}) {
|
||||||
|
if env.DISABLE_GA == "true" {
|
||||||
|
// do not send GA4 events in CI environment
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event := Event{
|
||||||
|
Name: name,
|
||||||
|
Params: params,
|
||||||
|
}
|
||||||
|
err := ga4Client.SendEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("send GA4 event failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
15
hrp/internal/sdk/ga4_test.go
Normal file
15
hrp/internal/sdk/ga4_test.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGA4(t *testing.T) {
|
||||||
|
ga4Client := NewGA4Client(ga4MeasurementID, ga4APISecret, false)
|
||||||
|
|
||||||
|
event := Event{
|
||||||
|
Name: "hrp_debug_event",
|
||||||
|
Params: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
ga4Client.SendEvent(event)
|
||||||
|
}
|
||||||
@@ -3,35 +3,23 @@ package sdk
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/denisbrodbeck/machineid"
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
trackingID = "UA-114587036-1" // Tracking ID for Google Analytics
|
sentryDSN = "https://cff5efc69b1a4325a4cf873f1e70c13a@o334324.ingest.sentry.io/6070292"
|
||||||
sentryDSN = "https://cff5efc69b1a4325a4cf873f1e70c13a@o334324.ingest.sentry.io/6070292"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var gaClient *GAClient
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// init GA client
|
|
||||||
clientID, err := machineid.ProtectedID("hrp")
|
|
||||||
if err != nil {
|
|
||||||
clientID = uuid.NewV1().String()
|
|
||||||
}
|
|
||||||
gaClient = NewGAClient(trackingID, clientID)
|
|
||||||
|
|
||||||
// init sentry sdk
|
// init sentry sdk
|
||||||
if env.DISABLE_SENTRY == "true" {
|
if env.DISABLE_SENTRY == "true" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = sentry.Init(sentry.ClientOptions{
|
err := sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: sentryDSN,
|
Dsn: sentryDSN,
|
||||||
Release: fmt.Sprintf("httprunner@%s", version.VERSION),
|
Release: fmt.Sprintf("httprunner@%s", version.VERSION),
|
||||||
AttachStacktrace: true,
|
AttachStacktrace: true,
|
||||||
@@ -43,15 +31,7 @@ func init() {
|
|||||||
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
||||||
scope.SetLevel(sentry.LevelError)
|
scope.SetLevel(sentry.LevelError)
|
||||||
scope.SetUser(sentry.User{
|
scope.SetUser(sentry.User{
|
||||||
ID: clientID,
|
ID: userID,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendEvent(e IEvent) error {
|
|
||||||
if env.DISABLE_GA == "true" {
|
|
||||||
// do not send GA events in CI environment
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return gaClient.SendEvent(e)
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
v4.3.4
|
v4.3.5
|
||||||
@@ -8,4 +8,4 @@ import (
|
|||||||
var VERSION string
|
var VERSION string
|
||||||
|
|
||||||
// httprunner python version
|
// httprunner python version
|
||||||
const HttpRunnerMinimumVersion = "v4.3.0"
|
const HttpRunnerMinimumVersion = "v4.3.5"
|
||||||
|
|||||||
@@ -4,14 +4,9 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func OpenWiki() error {
|
func OpenWiki() error {
|
||||||
sdk.SendEvent(sdk.EventTracking{
|
|
||||||
Category: "OpenWiki",
|
|
||||||
Action: "hrp wiki",
|
|
||||||
})
|
|
||||||
log.Info().Msgf("%s https://httprunner.com", openCmd)
|
log.Info().Msgf("%s https://httprunner.com", openCmd)
|
||||||
return myexec.RunCommand(openCmd, "https://httprunner.com")
|
return myexec.RunCommand(openCmd, "https://httprunner.com")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ const (
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
[
|
[
|
||||||
|
|
||||||
{"username": "test1", "password": "111111"},
|
{"username": "test1", "password": "111111"},
|
||||||
{"username": "test2", "password": "222222"},
|
{"username": "test2", "password": "222222"},
|
||||||
|
|
||||||
]
|
]
|
||||||
*/
|
*/
|
||||||
type Parameters []map[string]interface{}
|
type Parameters []map[string]interface{}
|
||||||
@@ -205,36 +207,38 @@ func genCartesianProduct(multiParameters []Parameters) Parameters {
|
|||||||
return cartesianProduct
|
return cartesianProduct
|
||||||
}
|
}
|
||||||
|
|
||||||
/* loadParameters loads parameters from multiple sources.
|
/*
|
||||||
|
loadParameters loads parameters from multiple sources.
|
||||||
|
|
||||||
parameter value may be in three types:
|
parameter value may be in three types:
|
||||||
|
|
||||||
(1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
|
(1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
|
||||||
(2) call built-in parameterize function, "${parameterize(account.csv)}"
|
(2) call built-in parameterize function, "${parameterize(account.csv)}"
|
||||||
(3) call custom function in debugtalk.py, "${gen_app_version()}"
|
(3) call custom function in debugtalk.py, "${gen_app_version()}"
|
||||||
|
|
||||||
configParameters = {
|
configParameters = {
|
||||||
"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"], // case 1
|
"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"], // case 1
|
||||||
"username-password": "${parameterize(account.csv)}", // case 2
|
"username-password": "${parameterize(account.csv)}", // case 2
|
||||||
"app_version": "${gen_app_version()}", // case 3
|
"app_version": "${gen_app_version()}", // case 3
|
||||||
}
|
}
|
||||||
|
|
||||||
=>
|
=>
|
||||||
|
|
||||||
{
|
{
|
||||||
"user_agent": [
|
"user_agent": [
|
||||||
{"user_agent": "iOS/10.1"},
|
{"user_agent": "iOS/10.1"},
|
||||||
{"user_agent": "iOS/10.2"},
|
{"user_agent": "iOS/10.2"},
|
||||||
{"user_agent": "iOS/10.3"},
|
{"user_agent": "iOS/10.3"},
|
||||||
],
|
],
|
||||||
"username-password": [
|
"username-password": [
|
||||||
{"username": "test1", "password": "111111"},
|
{"username": "test1", "password": "111111"},
|
||||||
{"username": "test2", "password": "222222"},
|
{"username": "test2", "password": "222222"},
|
||||||
],
|
],
|
||||||
"app_version": [
|
"app_version": [
|
||||||
{"app_version": "1.0.0"},
|
{"app_version": "1.0.0"},
|
||||||
{"app_version": "1.0.1"},
|
{"app_version": "1.0.1"},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
func (p *Parser) loadParameters(configParameters map[string]interface{}, variablesMapping map[string]interface{}) (
|
func (p *Parser) loadParameters(configParameters map[string]interface{}, variablesMapping map[string]interface{}) (
|
||||||
map[string]Parameters, error) {
|
map[string]Parameters, error) {
|
||||||
@@ -296,19 +300,23 @@ func (p *Parser) loadParameters(configParameters map[string]interface{}, variabl
|
|||||||
return parsedParameters, nil
|
return parsedParameters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/* convert parameters to standard format
|
/*
|
||||||
|
convert parameters to standard format
|
||||||
|
|
||||||
key and parametersRawList may be in three types:
|
key and parametersRawList may be in three types:
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
|
|
||||||
key = "user_agent"
|
key = "user_agent"
|
||||||
parametersRawList = ["iOS/10.1", "iOS/10.2"]
|
parametersRawList = ["iOS/10.1", "iOS/10.2"]
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
|
|
||||||
key = "username-password"
|
key = "username-password"
|
||||||
parametersRawList = [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}]
|
parametersRawList = [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}]
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
|
|
||||||
key = "username-password"
|
key = "username-password"
|
||||||
parametersRawList = [["test1", "111111"], ["test2", "222222"]]
|
parametersRawList = [["test1", "111111"], ["test2", "222222"]]
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -28,32 +28,48 @@ type Parser struct {
|
|||||||
plugin funplugin.IPlugin // plugin is used to call functions
|
plugin funplugin.IPlugin // plugin is used to call functions
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildURL(baseURL, stepURL string) string {
|
func buildURL(baseURL, stepURL string, queryParams url.Values) (fullUrl *url.URL) {
|
||||||
uStep, err := url.Parse(stepURL)
|
uStep, err := url.Parse(stepURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Str("stepURL", stepURL).Err(err).Msg("[buildURL] parse url failed")
|
log.Error().Str("stepURL", stepURL).Err(err).Msg("[buildURL] parse url failed")
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// append query params
|
||||||
|
if paramStr := queryParams.Encode(); paramStr != "" {
|
||||||
|
if uStep.RawQuery == "" {
|
||||||
|
uStep.RawQuery = paramStr
|
||||||
|
} else {
|
||||||
|
uStep.RawQuery = uStep.RawQuery + "&" + paramStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure path suffix '/' exists
|
||||||
|
if uStep.RawQuery == "" {
|
||||||
|
uStep.Path = strings.TrimRight(uStep.Path, "/") + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
fullUrl = uStep
|
||||||
|
}()
|
||||||
|
|
||||||
// step url is absolute url
|
// step url is absolute url
|
||||||
if uStep.Host != "" {
|
if uStep.Host != "" {
|
||||||
return stepURL
|
return uStep
|
||||||
}
|
}
|
||||||
|
|
||||||
// step url is relative, based on base url
|
// step url is relative, based on base url
|
||||||
uConfig, err := url.Parse(baseURL)
|
uConfig, err := url.Parse(baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Str("baseURL", baseURL).Err(err).Msg("[buildURL] parse url failed")
|
log.Error().Str("baseURL", baseURL).Err(err).Msg("[buildURL] parse url failed")
|
||||||
return ""
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge url
|
// merge url
|
||||||
uStep.Scheme = uConfig.Scheme
|
uStep.Scheme = uConfig.Scheme
|
||||||
uStep.Host = uConfig.Host
|
uStep.Host = uConfig.Host
|
||||||
uStep.Path = path.Join(uConfig.Path, uStep.Path)
|
uStep.Path = path.Join(uConfig.Path, uStep.Path)
|
||||||
|
return uStep
|
||||||
// base url missed
|
|
||||||
return uStep.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) ParseHeaders(rawHeaders map[string]string, variablesMapping map[string]interface{}) (map[string]string, error) {
|
func (p *Parser) ParseHeaders(rawHeaders map[string]string, variablesMapping map[string]interface{}) (map[string]string, error) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -9,60 +10,72 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildURL(t *testing.T) {
|
func TestBuildURL(t *testing.T) {
|
||||||
var url string
|
var preparedURL *url.URL
|
||||||
|
|
||||||
url = buildURL("https://postman-echo.com", "/get")
|
preparedURL = buildURL("https://postman-echo.com", "/get", nil)
|
||||||
if !assert.Equal(t, url, "https://postman-echo.com/get") {
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/get/") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
url = buildURL("https://postman-echo.com", "get")
|
preparedURL = buildURL("https://postman-echo.com", "get", nil)
|
||||||
if !assert.Equal(t, url, "https://postman-echo.com/get") {
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/get/") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
url = buildURL("https://postman-echo.com/", "/get")
|
preparedURL = buildURL("https://postman-echo.com/", "/get", nil)
|
||||||
if !assert.Equal(t, url, "https://postman-echo.com/get") {
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/get/") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
url = buildURL("https://postman-echo.com/abc/", "/get?a=1&b=2")
|
preparedURL = buildURL("https://postman-echo.com/abc/", "/get?a=1&b=2", nil)
|
||||||
if !assert.Equal(t, url, "https://postman-echo.com/abc/get?a=1&b=2") {
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/abc/get?a=1&b=2") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
url = buildURL("https://postman-echo.com/abc", "get?a=1&b=2")
|
preparedURL = buildURL("https://postman-echo.com/abc", "get?a=1&b=2", nil)
|
||||||
if !assert.Equal(t, url, "https://postman-echo.com/abc/get?a=1&b=2") {
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/abc/get?a=1&b=2") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
// omit query string in base url
|
// omit query string in base url
|
||||||
url = buildURL("https://postman-echo.com/abc?x=6&y=9", "/get?a=1&b=2")
|
preparedURL = buildURL("https://postman-echo.com/abc?x=6&y=9", "/get?a=1&b=2", nil)
|
||||||
if !assert.Equal(t, url, "https://postman-echo.com/abc/get?a=1&b=2") {
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/abc/get?a=1&b=2") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
url = buildURL("", "https://postman-echo.com/get")
|
preparedURL = buildURL("", "https://postman-echo.com/get", nil)
|
||||||
if !assert.Equal(t, url, "https://postman-echo.com/get") {
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/get/") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
// notice: step request url > config base url
|
// notice: step request url > config base url
|
||||||
url = buildURL("https://postman-echo.com", "https://httpbin.org/get")
|
preparedURL = buildURL("https://postman-echo.com", "https://httpbin.org/get", nil)
|
||||||
if !assert.Equal(t, url, "https://httpbin.org/get") {
|
if !assert.Equal(t, preparedURL.String(), "https://httpbin.org/get/") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
// websocket url
|
// websocket url
|
||||||
url = buildURL("wss://ws.postman-echo.com/raw", "")
|
preparedURL = buildURL("wss://ws.postman-echo.com/raw", "", nil)
|
||||||
if !assert.Equal(t, url, "wss://ws.postman-echo.com/raw") {
|
if !assert.Equal(t, preparedURL.String(), "wss://ws.postman-echo.com/raw/") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
url = buildURL("wss://ws.postman-echo.com", "/raw")
|
preparedURL = buildURL("wss://ws.postman-echo.com", "/raw", nil)
|
||||||
if !assert.Equal(t, url, "wss://ws.postman-echo.com/raw") {
|
if !assert.Equal(t, preparedURL.String(), "wss://ws.postman-echo.com/raw/") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
url = buildURL("wss://ws.postman-echo.com/raw", "ws://echo.websocket.events")
|
preparedURL = buildURL("wss://ws.postman-echo.com/raw", "ws://echo.websocket.events", nil)
|
||||||
if !assert.Equal(t, url, "ws://echo.websocket.events") {
|
if !assert.Equal(t, preparedURL.String(), "ws://echo.websocket.events/") {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := url.Values{}
|
||||||
|
queryParams.Add("c", "3")
|
||||||
|
queryParams.Add("d", "4")
|
||||||
|
preparedURL = buildURL("https://postman-echo.com/", "/get/", queryParams)
|
||||||
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/get?c=3&d=4") {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
preparedURL = buildURL("https://postman-echo.com/abc", "get?a=1&b=2", queryParams)
|
||||||
|
if !assert.Equal(t, preparedURL.String(), "https://postman-echo.com/abc/get?a=1&b=2&c=3&d=4") {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ func TestSpawnWorkersWithManyTasks(t *testing.T) {
|
|||||||
const numToSpawn int64 = 20
|
const numToSpawn int64 = 20
|
||||||
|
|
||||||
go runner.spawnWorkers(numToSpawn, float64(numToSpawn), runner.stopChan, runner.spawnComplete)
|
go runner.spawnWorkers(numToSpawn, float64(numToSpawn), runner.stopChan, runner.spawnComplete)
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
currentClients := runner.controller.getCurrentClientsNum()
|
currentClients := runner.controller.getCurrentClientsNum()
|
||||||
|
|
||||||
@@ -210,28 +210,29 @@ func TestSpawnWorkersWithManyTasks(t *testing.T) {
|
|||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
|
|
||||||
total := hundreds + tens + ones
|
total := hundreds + tens + ones
|
||||||
t.Logf("total tasks run: %d\n", total)
|
t.Logf("total tasks: %d, hundreds: %d, tens: %d, ones: %d\n",
|
||||||
|
total, hundreds, tens, ones)
|
||||||
|
|
||||||
assert.True(t, total > 111)
|
assert.True(t, total > 111)
|
||||||
|
|
||||||
assert.True(t, ones > 1)
|
assert.True(t, ones > 1)
|
||||||
actPercentage := float64(ones) / float64(total)
|
actPercentage := float64(ones) / float64(total)
|
||||||
expectedPercentage := 1.0 / 111.0
|
expectedPercentage := 1.0 / 111.0
|
||||||
if actPercentage > 2*expectedPercentage || actPercentage < 0.5*expectedPercentage {
|
if actPercentage > 4*expectedPercentage || actPercentage < 0.25*expectedPercentage {
|
||||||
t.Errorf("Unexpected percentage of ones task: exp %v, act %v", expectedPercentage, actPercentage)
|
t.Errorf("Unexpected percentage of ones task: exp %v, act %v", expectedPercentage, actPercentage)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.True(t, tens > 10)
|
assert.True(t, tens > 10)
|
||||||
actPercentage = float64(tens) / float64(total)
|
actPercentage = float64(tens) / float64(total)
|
||||||
expectedPercentage = 10.0 / 111.0
|
expectedPercentage = 10.0 / 111.0
|
||||||
if actPercentage > 2*expectedPercentage || actPercentage < 0.5*expectedPercentage {
|
if actPercentage > 4*expectedPercentage || actPercentage < 0.25*expectedPercentage {
|
||||||
t.Errorf("Unexpected percentage of tens task: exp %v, act %v", expectedPercentage, actPercentage)
|
t.Errorf("Unexpected percentage of tens task: exp %v, act %v", expectedPercentage, actPercentage)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.True(t, hundreds > 100)
|
assert.True(t, hundreds > 100)
|
||||||
actPercentage = float64(hundreds) / float64(total)
|
actPercentage = float64(hundreds) / float64(total)
|
||||||
expectedPercentage = 100.0 / 111.0
|
expectedPercentage = 100.0 / 111.0
|
||||||
if actPercentage > 2*expectedPercentage || actPercentage < 0.5*expectedPercentage {
|
if actPercentage > 1 || actPercentage < 0.25*expectedPercentage {
|
||||||
t.Errorf("Unexpected percentage of hundreds task: exp %v, act %v", expectedPercentage, actPercentage)
|
t.Errorf("Unexpected percentage of hundreds task: exp %v, act %v", expectedPercentage, actPercentage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,7 +260,7 @@ func TestSpawnAndStop(t *testing.T) {
|
|||||||
go runner.start()
|
go runner.start()
|
||||||
|
|
||||||
// wait for spawning goroutines
|
// wait for spawning goroutines
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
if runner.controller.getCurrentClientsNum() != 10 {
|
if runner.controller.getCurrentClientsNum() != 10 {
|
||||||
t.Error("Number of goroutines mismatches, expected: 10, current count", runner.controller.getCurrentClientsNum())
|
t.Error("Number of goroutines mismatches, expected: 10, current count", runner.controller.getCurrentClientsNum())
|
||||||
}
|
}
|
||||||
@@ -269,7 +270,6 @@ func TestSpawnAndStop(t *testing.T) {
|
|||||||
t.Error("Runner should send spawning_complete message when spawning completed, got", msg.Type)
|
t.Error("Runner should send spawning_complete message when spawning completed, got", msg.Type)
|
||||||
}
|
}
|
||||||
go runner.stop()
|
go runner.stop()
|
||||||
close(runner.doneChan)
|
|
||||||
|
|
||||||
runner.onQuiting()
|
runner.onQuiting()
|
||||||
msg = <-runner.client.sendChannel()
|
msg = <-runner.client.sendChannel()
|
||||||
@@ -384,7 +384,7 @@ func TestOnMessage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// spawn complete and running
|
// spawn complete and running
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
if runner.controller.getCurrentClientsNum() != 10 {
|
if runner.controller.getCurrentClientsNum() != 10 {
|
||||||
t.Error("Number of goroutines mismatches, expected: 10, current count:", runner.controller.getCurrentClientsNum())
|
t.Error("Number of goroutines mismatches, expected: 10, current count:", runner.controller.getCurrentClientsNum())
|
||||||
}
|
}
|
||||||
@@ -430,7 +430,7 @@ func TestOnMessage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// spawn complete and running
|
// spawn complete and running
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
if runner.controller.getCurrentClientsNum() != 10 {
|
if runner.controller.getCurrentClientsNum() != 10 {
|
||||||
t.Error("Number of goroutines mismatches, expected: 10, current count:", runner.controller.getCurrentClientsNum())
|
t.Error("Number of goroutines mismatches, expected: 10, current count:", runner.controller.getCurrentClientsNum())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package convert
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
@@ -139,19 +139,25 @@ func (c *TCaseConverter) loadCase(casePath string, fromType FromType) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TCaseConverter) Convert(casePath string, fromType FromType, outputType OutputType) error {
|
func (c *TCaseConverter) Convert(casePath string, fromType FromType, outputType OutputType) (err error) {
|
||||||
// report event
|
// report GA event
|
||||||
sdk.SendEvent(sdk.EventTracking{
|
startTime := time.Now()
|
||||||
Category: "ConvertTests",
|
defer func() {
|
||||||
Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()),
|
sdk.SendGA4Event("hrp_convert", map[string]interface{}{
|
||||||
})
|
"from": fromType.String(),
|
||||||
|
"to": outputType.String(),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
log.Info().Str("path", casePath).
|
log.Info().Str("path", casePath).
|
||||||
Str("fromType", fromType.String()).
|
Str("fromType", fromType.String()).
|
||||||
Str("outputType", outputType.String()).
|
Str("outputType", outputType.String()).
|
||||||
Msg("convert testcase")
|
Msg("convert testcase")
|
||||||
|
|
||||||
// load source file
|
// load source file
|
||||||
err := c.loadCase(casePath, fromType)
|
err = c.loadCase(casePath, fromType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,8 +555,9 @@ func (s UiSelectorHelper) Index(index int) UiSelectorHelper {
|
|||||||
// 2, the `className(String)` matches the image
|
// 2, the `className(String)` matches the image
|
||||||
// widget class, and `enabled(boolean)` is true.
|
// widget class, and `enabled(boolean)` is true.
|
||||||
// The code would look like this:
|
// The code would look like this:
|
||||||
// `new UiSelector().className("android.widget.ImageView")
|
//
|
||||||
// .enabled(true).instance(2);`
|
// `new UiSelector().className("android.widget.ImageView")
|
||||||
|
// .enabled(true).instance(2);`
|
||||||
func (s UiSelectorHelper) Instance(instance int) UiSelectorHelper {
|
func (s UiSelectorHelper) Instance(instance int) UiSelectorHelper {
|
||||||
s.value.WriteString(fmt.Sprintf(`.instance(%d)`, instance))
|
s.value.WriteString(fmt.Sprintf(`.instance(%d)`, instance))
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -261,7 +261,8 @@ func (ud *uiaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...Action
|
|||||||
// Swipe performs a swipe from one coordinate to another using the number of steps
|
// Swipe performs a swipe from one coordinate to another using the number of steps
|
||||||
// to determine smoothness and speed. Each step execution is throttled to 5ms
|
// to determine smoothness and speed. Each step execution is throttled to 5ms
|
||||||
// per step. So for a 100 steps, the swipe will take about 1/2 second to complete.
|
// per step. So for a 100 steps, the swipe will take about 1/2 second to complete.
|
||||||
// `steps` is the number of move steps sent to the system
|
//
|
||||||
|
// `steps` is the number of move steps sent to the system
|
||||||
func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error {
|
func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error {
|
||||||
return ud.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
|
return ud.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ func (caps Capabilities) WithDefaultAlertAction(alertAction AlertAction) Capabil
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithMaxTypingFrequency
|
// WithMaxTypingFrequency
|
||||||
// Defaults to `60`.
|
//
|
||||||
|
// Defaults to `60`.
|
||||||
func (caps Capabilities) WithMaxTypingFrequency(n int) Capabilities {
|
func (caps Capabilities) WithMaxTypingFrequency(n int) Capabilities {
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
n = 60
|
n = 60
|
||||||
@@ -41,21 +42,24 @@ func (caps Capabilities) WithMaxTypingFrequency(n int) Capabilities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithWaitForIdleTimeout
|
// WithWaitForIdleTimeout
|
||||||
// Defaults to `10`
|
//
|
||||||
|
// Defaults to `10`
|
||||||
func (caps Capabilities) WithWaitForIdleTimeout(second float64) Capabilities {
|
func (caps Capabilities) WithWaitForIdleTimeout(second float64) Capabilities {
|
||||||
caps["waitForIdleTimeout"] = second
|
caps["waitForIdleTimeout"] = second
|
||||||
return caps
|
return caps
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithShouldUseTestManagerForVisibilityDetection If set to YES will ask TestManagerDaemon for element visibility
|
// WithShouldUseTestManagerForVisibilityDetection If set to YES will ask TestManagerDaemon for element visibility
|
||||||
// Defaults to `false`
|
//
|
||||||
|
// Defaults to `false`
|
||||||
func (caps Capabilities) WithShouldUseTestManagerForVisibilityDetection(b bool) Capabilities {
|
func (caps Capabilities) WithShouldUseTestManagerForVisibilityDetection(b bool) Capabilities {
|
||||||
caps["shouldUseTestManagerForVisibilityDetection"] = b
|
caps["shouldUseTestManagerForVisibilityDetection"] = b
|
||||||
return caps
|
return caps
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithShouldUseCompactResponses If set to YES will use compact (standards-compliant) & faster responses
|
// WithShouldUseCompactResponses If set to YES will use compact (standards-compliant) & faster responses
|
||||||
// Defaults to `true`
|
//
|
||||||
|
// Defaults to `true`
|
||||||
func (caps Capabilities) WithShouldUseCompactResponses(b bool) Capabilities {
|
func (caps Capabilities) WithShouldUseCompactResponses(b bool) Capabilities {
|
||||||
caps["shouldUseCompactResponses"] = b
|
caps["shouldUseCompactResponses"] = b
|
||||||
return caps
|
return caps
|
||||||
@@ -63,28 +67,32 @@ func (caps Capabilities) WithShouldUseCompactResponses(b bool) Capabilities {
|
|||||||
|
|
||||||
// WithElementResponseAttributes If shouldUseCompactResponses == NO,
|
// WithElementResponseAttributes If shouldUseCompactResponses == NO,
|
||||||
// is the comma-separated list of fields to return with each element.
|
// is the comma-separated list of fields to return with each element.
|
||||||
// Defaults to `type,label`.
|
//
|
||||||
|
// Defaults to `type,label`.
|
||||||
func (caps Capabilities) WithElementResponseAttributes(s string) Capabilities {
|
func (caps Capabilities) WithElementResponseAttributes(s string) Capabilities {
|
||||||
caps["elementResponseAttributes"] = s
|
caps["elementResponseAttributes"] = s
|
||||||
return caps
|
return caps
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithShouldUseSingletonTestManager
|
// WithShouldUseSingletonTestManager
|
||||||
// Defaults to `true`
|
//
|
||||||
|
// Defaults to `true`
|
||||||
func (caps Capabilities) WithShouldUseSingletonTestManager(b bool) Capabilities {
|
func (caps Capabilities) WithShouldUseSingletonTestManager(b bool) Capabilities {
|
||||||
caps["shouldUseSingletonTestManager"] = b
|
caps["shouldUseSingletonTestManager"] = b
|
||||||
return caps
|
return caps
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithDisableAutomaticScreenshots
|
// WithDisableAutomaticScreenshots
|
||||||
// Defaults to `true`
|
//
|
||||||
|
// Defaults to `true`
|
||||||
func (caps Capabilities) WithDisableAutomaticScreenshots(b bool) Capabilities {
|
func (caps Capabilities) WithDisableAutomaticScreenshots(b bool) Capabilities {
|
||||||
caps["disableAutomaticScreenshots"] = b
|
caps["disableAutomaticScreenshots"] = b
|
||||||
return caps
|
return caps
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithShouldTerminateApp
|
// WithShouldTerminateApp
|
||||||
// Defaults to `true`
|
//
|
||||||
|
// Defaults to `true`
|
||||||
func (caps Capabilities) WithShouldTerminateApp(b bool) Capabilities {
|
func (caps Capabilities) WithShouldTerminateApp(b bool) Capabilities {
|
||||||
caps["shouldTerminateApp"] = b
|
caps["shouldTerminateApp"] = b
|
||||||
return caps
|
return caps
|
||||||
@@ -376,7 +384,8 @@ func (opt SourceOption) WithFormatAsDescription() SourceOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithScope Allows to provide XML scope.
|
// WithScope Allows to provide XML scope.
|
||||||
// only `xml` is supported.
|
//
|
||||||
|
// only `xml` is supported.
|
||||||
func (opt SourceOption) WithScope(scope string) SourceOption {
|
func (opt SourceOption) WithScope(scope string) SourceOption {
|
||||||
if vFormat, ok := opt["format"]; ok && vFormat != "xml" {
|
if vFormat, ok := opt["format"]; ok && vFormat != "xml" {
|
||||||
return opt
|
return opt
|
||||||
|
|||||||
@@ -176,11 +176,12 @@ func newVEDEMImageService(actions ...string) (*veDEMImageService, error) {
|
|||||||
|
|
||||||
// veDEMImageService implements IImageService interface
|
// veDEMImageService implements IImageService interface
|
||||||
// actions:
|
// actions:
|
||||||
// ocr - get ocr texts
|
//
|
||||||
// upload - get image uploaded url
|
// ocr - get ocr texts
|
||||||
// liveType - get live type
|
// upload - get image uploaded url
|
||||||
// popup - get popup windows
|
// liveType - get live type
|
||||||
// close - get close popup
|
// popup - get popup windows
|
||||||
|
// close - get close popup
|
||||||
type veDEMImageService struct {
|
type veDEMImageService struct {
|
||||||
actions []string
|
actions []string
|
||||||
}
|
}
|
||||||
@@ -230,10 +231,6 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer) (
|
|||||||
req.Header.Add("Agw-Auth-Content", signToken)
|
req.Header.Add("Agw-Auth-Content", signToken)
|
||||||
req.Header.Add("Content-Type", bodyWriter.FormDataContentType())
|
req.Header.Add("Content-Type", bodyWriter.FormDataContentType())
|
||||||
|
|
||||||
// ppe
|
|
||||||
// req.Header.Add("x-use-ppe", "1")
|
|
||||||
// req.Header.Add("x-tt-env", "ppe_vedem_algorithm")
|
|
||||||
|
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
// retry 3 times
|
// retry 3 times
|
||||||
for i := 1; i <= 3; i++ {
|
for i := 1; i <= 3; i++ {
|
||||||
|
|||||||
@@ -90,15 +90,14 @@ func initPlugin(path, venv string, logOn bool) (plugin funplugin.IPlugin, err er
|
|||||||
pluginMap.Store(pluginPath, plugin)
|
pluginMap.Store(pluginPath, plugin)
|
||||||
|
|
||||||
// report event for initializing plugin
|
// report event for initializing plugin
|
||||||
event := sdk.EventTracking{
|
params := map[string]interface{}{
|
||||||
Category: "InitPlugin",
|
"type": plugin.Type(),
|
||||||
Action: fmt.Sprintf("Init %s plugin", plugin.Type()),
|
"result": "success",
|
||||||
Value: 0, // success
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
event.Value = 1 // failed
|
params["result"] = "failed"
|
||||||
}
|
}
|
||||||
go sdk.SendEvent(event)
|
go sdk.SendGA4Event("init_plugin", params)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,14 +194,16 @@ func (r *HRPRunner) GenHTMLReport() *HRPRunner {
|
|||||||
// Run starts to execute one or multiple testcases.
|
// Run starts to execute one or multiple testcases.
|
||||||
func (r *HRPRunner) Run(testcases ...ITestCase) (err error) {
|
func (r *HRPRunner) Run(testcases ...ITestCase) (err error) {
|
||||||
log.Info().Str("hrp_version", version.VERSION).Msg("start running")
|
log.Info().Str("hrp_version", version.VERSION).Msg("start running")
|
||||||
event := sdk.EventTracking{
|
|
||||||
Category: "RunAPITests",
|
startTime := time.Now()
|
||||||
Action: "hrp run",
|
defer func() {
|
||||||
}
|
// report run event
|
||||||
// report start event
|
sdk.SendGA4Event("hrp_run", map[string]interface{}{
|
||||||
go sdk.SendEvent(event)
|
"success": err == nil,
|
||||||
// report execution timing event
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
defer sdk.SendEvent(event.StartTiming("execution"))
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
// record execution data to summary
|
// record execution data to summary
|
||||||
s := newOutSummary()
|
s := newOutSummary()
|
||||||
|
|
||||||
@@ -511,6 +513,9 @@ func (r *SessionRunner) inheritConnection(src *SessionRunner) {
|
|||||||
// Start runs the test steps in sequential order.
|
// Start runs the test steps in sequential order.
|
||||||
// givenVars is used for data driven
|
// givenVars is used for data driven
|
||||||
func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
|
func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
|
||||||
|
// report GA event
|
||||||
|
sdk.SendGA4Event("hrp_session_runner_start", nil)
|
||||||
|
|
||||||
config := r.caseRunner.testCase.Config
|
config := r.caseRunner.testCase.Config
|
||||||
log.Info().Str("testcase", config.Name).Msg("run testcase start")
|
log.Info().Str("testcase", config.Name).Msg("run testcase start")
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildHashicorpGoPlugin() {
|
func buildHashicorpGoPlugin() {
|
||||||
@@ -63,31 +64,26 @@ func assertRunTestCases(t *testing.T) {
|
|||||||
refCase := TestCasePath(demoTestCaseWithPluginJSONPath)
|
refCase := TestCasePath(demoTestCaseWithPluginJSONPath)
|
||||||
testcase1 := &TestCase{
|
testcase1 := &TestCase{
|
||||||
Config: NewConfig("TestCase1").
|
Config: NewConfig("TestCase1").
|
||||||
SetBaseURL("https://httpbin.org"),
|
SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []IStep{
|
||||||
NewStep("testcase1-step1").
|
NewStep("testcase1-step1").
|
||||||
GET("/headers").
|
GET("/headers").
|
||||||
Validate().
|
Validate().
|
||||||
AssertEqual("status_code", 200, "check status code").
|
AssertEqual("status_code", 200, "check status code").
|
||||||
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
|
AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check http response Content-Type"),
|
||||||
NewStep("testcase1-step2").
|
NewStep("testcase1-step2").CallRefCase(
|
||||||
GET("/user-agent").
|
|
||||||
Validate().
|
|
||||||
AssertEqual("status_code", 200, "check status code").
|
|
||||||
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
|
|
||||||
NewStep("testcase1-step3").CallRefCase(
|
|
||||||
&TestCase{
|
&TestCase{
|
||||||
Config: NewConfig("testcase1-step3-ref-case").SetBaseURL("https://httpbin.org"),
|
Config: NewConfig("testcase1-step3-ref-case").SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []IStep{
|
||||||
NewStep("ip").
|
NewStep("ip").
|
||||||
GET("/ip").
|
GET("/ip").
|
||||||
Validate().
|
Validate().
|
||||||
AssertEqual("status_code", 200, "check status code").
|
AssertEqual("status_code", 200, "check status code").
|
||||||
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
|
AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check http response Content-Type"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
NewStep("testcase1-step4").CallRefCase(&refCase),
|
NewStep("testcase1-step3").CallRefCase(&refCase),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testcase2 := &TestCase{
|
testcase2 := &TestCase{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -564,6 +565,11 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
mobileStep = step.Android
|
mobileStep = step.Android
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// report GA event
|
||||||
|
sdk.SendGA4Event("hrp_run_ui", map[string]interface{}{
|
||||||
|
"osType": osType,
|
||||||
|
})
|
||||||
|
|
||||||
stepResult = &StepResult{
|
stepResult = &StepResult{
|
||||||
Name: step.Name,
|
Name: step.Name,
|
||||||
StepType: StepType(osType),
|
StepType: StepType(osType),
|
||||||
|
|||||||
@@ -149,9 +149,8 @@ func (r *requestBuilder) prepareUrlParams(stepVariables map[string]interface{})
|
|||||||
}
|
}
|
||||||
var baseURL string
|
var baseURL string
|
||||||
if stepVariables["base_url"] != nil {
|
if stepVariables["base_url"] != nil {
|
||||||
baseURL = stepVariables["base_url"].(string)
|
baseURL, _ = stepVariables["base_url"].(string)
|
||||||
}
|
}
|
||||||
rawUrl := buildURL(baseURL, convertString(requestUrl))
|
|
||||||
|
|
||||||
// prepare request params
|
// prepare request params
|
||||||
var queryParams url.Values
|
var queryParams url.Values
|
||||||
@@ -161,35 +160,24 @@ func (r *requestBuilder) prepareUrlParams(stepVariables map[string]interface{})
|
|||||||
return errors.Wrap(err, "parse request params failed")
|
return errors.Wrap(err, "parse request params failed")
|
||||||
}
|
}
|
||||||
parsedParams := params.(map[string]interface{})
|
parsedParams := params.(map[string]interface{})
|
||||||
r.requestMap["params"] = parsedParams
|
|
||||||
if len(parsedParams) > 0 {
|
if len(parsedParams) > 0 {
|
||||||
queryParams = make(url.Values)
|
queryParams = make(url.Values)
|
||||||
for k, v := range parsedParams {
|
for k, v := range parsedParams {
|
||||||
queryParams.Add(k, convertString(v))
|
queryParams.Add(k, convertString(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if queryParams != nil {
|
// request params has been appended to url, thus delete it here
|
||||||
// append params to url
|
delete(r.requestMap, "params")
|
||||||
paramStr := queryParams.Encode()
|
|
||||||
if strings.IndexByte(rawUrl, '?') == -1 {
|
|
||||||
rawUrl = rawUrl + "?" + paramStr
|
|
||||||
} else {
|
|
||||||
rawUrl = rawUrl + "&" + paramStr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare url
|
// prepare url
|
||||||
u, err := url.Parse(rawUrl)
|
preparedURL := buildURL(baseURL, convertString(requestUrl), queryParams)
|
||||||
if err != nil {
|
r.req.URL = preparedURL
|
||||||
return errors.Wrap(err, "parse url failed")
|
r.req.Host = preparedURL.Host
|
||||||
}
|
|
||||||
r.req.URL = u
|
|
||||||
r.req.Host = u.Host
|
|
||||||
|
|
||||||
// update url
|
// update url
|
||||||
r.requestMap["url"] = u.String()
|
r.requestMap["url"] = preparedURL.String()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,43 +328,14 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
// add request object to step variables, could be used in setup hooks
|
// add request object to step variables, could be used in setup hooks
|
||||||
stepVariables["hrp_step_name"] = step.Name
|
stepVariables["hrp_step_name"] = step.Name
|
||||||
stepVariables["hrp_step_request"] = rb.requestMap
|
stepVariables["hrp_step_request"] = rb.requestMap
|
||||||
stepVariables["request"] = rb.requestMap
|
stepVariables["request"] = rb.requestMap // setup hooks compatible with v3
|
||||||
|
|
||||||
// deal with setup hooks
|
// deal with setup hooks
|
||||||
for _, setupHook := range step.SetupHooks {
|
for _, setupHook := range step.SetupHooks {
|
||||||
req, err := parser.Parse(setupHook, stepVariables)
|
_, err := parser.Parse(setupHook, stepVariables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stepResult, errors.Wrap(err, "run setup hooks failed")
|
return stepResult, errors.Wrap(err, "run setup hooks failed")
|
||||||
}
|
}
|
||||||
reqMap, ok := req.(map[string]interface{})
|
|
||||||
if ok && reqMap != nil {
|
|
||||||
rb.requestMap = reqMap
|
|
||||||
stepVariables["request"] = reqMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(step.SetupHooks) > 0 {
|
|
||||||
requestBody, ok := rb.requestMap["body"].(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
body, err := json.Marshal(requestBody)
|
|
||||||
if err == nil {
|
|
||||||
rb.req.Body = io.NopCloser(bytes.NewReader(body))
|
|
||||||
rb.req.ContentLength = int64(len(body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestParams, ok := rb.requestMap["params"].(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
params, err := json.Marshal(requestParams)
|
|
||||||
if err == nil {
|
|
||||||
rb.req.URL.RawQuery = string(params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestHeaders, ok := rb.requestMap["headers"].(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
rb.req.Header = http.Header{}
|
|
||||||
for k, v := range requestHeaders {
|
|
||||||
rb.req.Header.Set(k, v.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// log & print request
|
// log & print request
|
||||||
@@ -451,15 +410,10 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
|
|
||||||
// deal with teardown hooks
|
// deal with teardown hooks
|
||||||
for _, teardownHook := range step.TeardownHooks {
|
for _, teardownHook := range step.TeardownHooks {
|
||||||
res, err := parser.Parse(teardownHook, stepVariables)
|
_, err := parser.Parse(teardownHook, stepVariables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stepResult, errors.Wrap(err, "run teardown hooks failed")
|
return stepResult, errors.Wrap(err, "run teardown hooks failed")
|
||||||
}
|
}
|
||||||
resMpa, ok := res.(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
stepVariables["response"] = resMpa
|
|
||||||
respObj.respObjMeta = resMpa
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionData.ReqResps.Request = rb.requestMap
|
sessionData.ReqResps.Request = rb.requestMap
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func TestRunRequestStatOn(t *testing.T) {
|
|||||||
if !assert.Greater(t, stat["TLSHandshake"], int64(0)) {
|
if !assert.Greater(t, stat["TLSHandshake"], int64(0)) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
if !assert.Greater(t, stat["ServerProcessing"], int64(1)) {
|
if !assert.Greater(t, stat["ServerProcessing"], int64(0)) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) {
|
if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) {
|
||||||
@@ -165,7 +165,7 @@ func TestRunCaseWithTimeout(t *testing.T) {
|
|||||||
testcase1 := &TestCase{
|
testcase1 := &TestCase{
|
||||||
Config: NewConfig("TestCase1").
|
Config: NewConfig("TestCase1").
|
||||||
SetRequestTimeout(10). // set global timeout to 10s
|
SetRequestTimeout(10). // set global timeout to 10s
|
||||||
SetBaseURL("https://httpbin.org"),
|
SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []IStep{
|
||||||
NewStep("step1").
|
NewStep("step1").
|
||||||
GET("/delay/1").
|
GET("/delay/1").
|
||||||
@@ -180,11 +180,11 @@ func TestRunCaseWithTimeout(t *testing.T) {
|
|||||||
|
|
||||||
testcase2 := &TestCase{
|
testcase2 := &TestCase{
|
||||||
Config: NewConfig("TestCase2").
|
Config: NewConfig("TestCase2").
|
||||||
SetRequestTimeout(10). // set global timeout to 10s
|
SetRequestTimeout(5). // set global timeout to 10s
|
||||||
SetBaseURL("https://httpbin.org"),
|
SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []IStep{
|
||||||
NewStep("step1").
|
NewStep("step1").
|
||||||
GET("/delay/11").
|
GET("/delay/10").
|
||||||
Validate().
|
Validate().
|
||||||
AssertEqual("status_code", 200, "check status code"),
|
AssertEqual("status_code", 200, "check status code"),
|
||||||
},
|
},
|
||||||
@@ -198,7 +198,7 @@ func TestRunCaseWithTimeout(t *testing.T) {
|
|||||||
testcase3 := &TestCase{
|
testcase3 := &TestCase{
|
||||||
Config: NewConfig("TestCase3").
|
Config: NewConfig("TestCase3").
|
||||||
SetRequestTimeout(10).
|
SetRequestTimeout(10).
|
||||||
SetBaseURL("https://httpbin.org"),
|
SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []IStep{
|
||||||
NewStep("step2").
|
NewStep("step2").
|
||||||
GET("/delay/11").
|
GET("/delay/11").
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build localtest
|
||||||
|
|
||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = "v4.3.0"
|
__version__ = "v4.3.5"
|
||||||
__description__ = "One-stop solution for HTTP(S) testing."
|
__description__ = "One-stop solution for HTTP(S) testing."
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from loguru import logger
|
|||||||
from httprunner import __description__, __version__
|
from httprunner import __description__, __version__
|
||||||
from httprunner.compat import ensure_cli_args
|
from httprunner.compat import ensure_cli_args
|
||||||
from httprunner.make import init_make_parser, main_make
|
from httprunner.make import init_make_parser, main_make
|
||||||
from httprunner.utils import ga_client, init_logger, init_sentry_sdk
|
from httprunner.utils import ga4_client, init_logger, init_sentry_sdk
|
||||||
|
|
||||||
init_sentry_sdk()
|
init_sentry_sdk()
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ def init_parser_run(subparsers):
|
|||||||
|
|
||||||
|
|
||||||
def main_run(extra_args) -> enum.IntEnum:
|
def main_run(extra_args) -> enum.IntEnum:
|
||||||
ga_client.track_event("RunAPITests", "hrun")
|
ga4_client.send_event("hrun")
|
||||||
# keep compatibility with v2
|
# keep compatibility with v2
|
||||||
extra_args = ensure_cli_args(extra_args)
|
extra_args = ensure_cli_args(extra_args)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from httprunner.client import HttpSession
|
from httprunner.client import HttpSession
|
||||||
|
from httprunner.utils import HTTP_BIN_URL
|
||||||
|
|
||||||
|
|
||||||
class TestHttpSession(unittest.TestCase):
|
class TestHttpSession(unittest.TestCase):
|
||||||
@@ -8,15 +9,15 @@ class TestHttpSession(unittest.TestCase):
|
|||||||
self.session = HttpSession()
|
self.session = HttpSession()
|
||||||
|
|
||||||
def test_request_http(self):
|
def test_request_http(self):
|
||||||
self.session.request("get", "https://httpbin.org/get")
|
self.session.request("get", f"{HTTP_BIN_URL}/get")
|
||||||
address = self.session.data.address
|
address = self.session.data.address
|
||||||
self.assertGreater(len(address.server_ip), 0)
|
self.assertGreater(len(address.server_ip), 0)
|
||||||
self.assertEqual(address.server_port, 443)
|
self.assertEqual(address.server_port, 80)
|
||||||
self.assertGreater(len(address.client_ip), 0)
|
self.assertGreater(len(address.client_ip), 0)
|
||||||
self.assertGreater(address.client_port, 10000)
|
self.assertGreater(address.client_port, 10000)
|
||||||
|
|
||||||
def test_request_https(self):
|
def test_request_https(self):
|
||||||
self.session.request("get", "https://httpbin.org/get")
|
self.session.request("get", "https://postman-echo.com/get")
|
||||||
address = self.session.data.address
|
address = self.session.data.address
|
||||||
self.assertGreater(len(address.server_ip), 0)
|
self.assertGreater(len(address.server_ip), 0)
|
||||||
self.assertEqual(address.server_port, 443)
|
self.assertEqual(address.server_port, 443)
|
||||||
@@ -26,7 +27,7 @@ class TestHttpSession(unittest.TestCase):
|
|||||||
def test_request_http_allow_redirects(self):
|
def test_request_http_allow_redirects(self):
|
||||||
self.session.request(
|
self.session.request(
|
||||||
"get",
|
"get",
|
||||||
"https://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
|
f"{HTTP_BIN_URL}/redirect-to?url=https%3A%2F%2Fgithub.com",
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
)
|
)
|
||||||
address = self.session.data.address
|
address = self.session.data.address
|
||||||
@@ -38,7 +39,7 @@ class TestHttpSession(unittest.TestCase):
|
|||||||
def test_request_https_allow_redirects(self):
|
def test_request_https_allow_redirects(self):
|
||||||
self.session.request(
|
self.session.request(
|
||||||
"get",
|
"get",
|
||||||
"https://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
|
"https://postman-echo.com/redirect-to?url=https%3A%2F%2Fgithub.com",
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
)
|
)
|
||||||
address = self.session.data.address
|
address = self.session.data.address
|
||||||
@@ -50,7 +51,7 @@ class TestHttpSession(unittest.TestCase):
|
|||||||
def test_request_http_not_allow_redirects(self):
|
def test_request_http_not_allow_redirects(self):
|
||||||
self.session.request(
|
self.session.request(
|
||||||
"get",
|
"get",
|
||||||
"https://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
|
f"{HTTP_BIN_URL}/redirect-to?url=https%3A%2F%2Fgithub.com",
|
||||||
allow_redirects=False,
|
allow_redirects=False,
|
||||||
)
|
)
|
||||||
address = self.session.data.address
|
address = self.session.data.address
|
||||||
@@ -62,7 +63,7 @@ class TestHttpSession(unittest.TestCase):
|
|||||||
def test_request_https_not_allow_redirects(self):
|
def test_request_https_not_allow_redirects(self):
|
||||||
self.session.request(
|
self.session.request(
|
||||||
"get",
|
"get",
|
||||||
"https://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
|
"https://postman-echo.com/redirect-to?url=https%3A%2F%2Fgithub.com",
|
||||||
allow_redirects=False,
|
allow_redirects=False,
|
||||||
)
|
)
|
||||||
address = self.session.data.address
|
address = self.session.data.address
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import os
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from httprunner import compat, exceptions, loader
|
from httprunner import compat, exceptions, loader
|
||||||
|
from httprunner.utils import HTTP_BIN_URL
|
||||||
|
|
||||||
|
|
||||||
class TestCompat(unittest.TestCase):
|
class TestCompat(unittest.TestCase):
|
||||||
@@ -155,7 +156,7 @@ class TestCompat(unittest.TestCase):
|
|||||||
|
|
||||||
def test_ensure_testcase_v4(self):
|
def test_ensure_testcase_v4(self):
|
||||||
testcase_content = {
|
testcase_content = {
|
||||||
"config": {"name": "xxx", "base_url": "https://httpbin.org"},
|
"config": {"name": "xxx", "base_url": HTTP_BIN_URL},
|
||||||
"teststeps": [
|
"teststeps": [
|
||||||
{
|
{
|
||||||
"name": "get with params",
|
"name": "get with params",
|
||||||
@@ -179,7 +180,7 @@ class TestCompat(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
compat.ensure_testcase_v4(testcase_content),
|
compat.ensure_testcase_v4(testcase_content),
|
||||||
{
|
{
|
||||||
"config": {"name": "xxx", "base_url": "https://httpbin.org"},
|
"config": {"name": "xxx", "base_url": HTTP_BIN_URL},
|
||||||
"teststeps": [
|
"teststeps": [
|
||||||
{
|
{
|
||||||
"name": "get with params",
|
"name": "get with params",
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import sys
|
|||||||
from typing import Text
|
from typing import Text
|
||||||
|
|
||||||
from httprunner.models import VariablesMapping, FunctionsMapping, TStep
|
from httprunner.models import VariablesMapping, FunctionsMapping, TStep
|
||||||
from httprunner.parser import parse_data, parse_variables_mapping
|
from httprunner.parser import parse_data
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -138,7 +138,6 @@ def multipart_encoder(**kwargs):
|
|||||||
ensure_upload_ready()
|
ensure_upload_ready()
|
||||||
fields_dict = {}
|
fields_dict = {}
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
|
|
||||||
if os.path.isabs(value):
|
if os.path.isabs(value):
|
||||||
# value is absolute file path
|
# value is absolute file path
|
||||||
_file_path = value
|
_file_path = value
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from httprunner.loader import (
|
|||||||
load_testcase,
|
load_testcase,
|
||||||
)
|
)
|
||||||
from httprunner.response import uniform_validator
|
from httprunner.response import uniform_validator
|
||||||
from httprunner.utils import ga_client, is_support_multiprocessing
|
from httprunner.utils import ga4_client, is_support_multiprocessing
|
||||||
|
|
||||||
""" cache converted pytest files, avoid duplicate making
|
""" cache converted pytest files, avoid duplicate making
|
||||||
"""
|
"""
|
||||||
@@ -541,7 +541,7 @@ def main_make(tests_paths: List[Text]) -> List[Text]:
|
|||||||
if not tests_paths:
|
if not tests_paths:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
ga_client.track_event("ConvertTests", "hmake")
|
ga4_client.send_event("hmake")
|
||||||
|
|
||||||
for tests_path in tests_paths:
|
for tests_path in tests_paths:
|
||||||
tests_path = ensure_path_sep(tests_path)
|
tests_path = ensure_path_sep(tests_path)
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import requests
|
|||||||
|
|
||||||
from httprunner.parser import Parser
|
from httprunner.parser import Parser
|
||||||
from httprunner.response import ResponseObject, uniform_validator
|
from httprunner.response import ResponseObject, uniform_validator
|
||||||
|
from httprunner.utils import HTTP_BIN_URL
|
||||||
|
|
||||||
|
|
||||||
class TestResponse(unittest.TestCase):
|
class TestResponse(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
"https://httpbin.org/anything",
|
f"{HTTP_BIN_URL}/anything",
|
||||||
json={
|
json={
|
||||||
"locations": [
|
"locations": [
|
||||||
{"name": "Seattle", "state": "WA"},
|
{"name": "Seattle", "state": "WA"},
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from httprunner.models import (
|
|||||||
VariablesMapping,
|
VariablesMapping,
|
||||||
)
|
)
|
||||||
from httprunner.parser import Parser
|
from httprunner.parser import Parser
|
||||||
from httprunner.utils import LOGGER_FORMAT, merge_variables
|
from httprunner.utils import LOGGER_FORMAT, merge_variables, ga4_client
|
||||||
|
|
||||||
|
|
||||||
class SessionRunner(object):
|
class SessionRunner(object):
|
||||||
@@ -210,6 +210,7 @@ class SessionRunner(object):
|
|||||||
|
|
||||||
def test_start(self, param: Dict = None) -> "SessionRunner":
|
def test_start(self, param: Dict = None) -> "SessionRunner":
|
||||||
"""main entrance, discovered by pytest"""
|
"""main entrance, discovered by pytest"""
|
||||||
|
ga4_client.send_event("test_start")
|
||||||
print("\n")
|
print("\n")
|
||||||
self.__init()
|
self.__init()
|
||||||
self.__parse_config(param)
|
self.__parse_config(param)
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import json
|
|||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import platform
|
import platform
|
||||||
|
import random
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from multiprocessing import Queue
|
from multiprocessing import Queue
|
||||||
from typing import Any, Dict, List, Text
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
@@ -18,6 +20,25 @@ from httprunner import __version__, exceptions
|
|||||||
from httprunner.models import VariablesMapping
|
from httprunner.models import VariablesMapping
|
||||||
|
|
||||||
|
|
||||||
|
""" run httpbin as test service
|
||||||
|
https://github.com/postmanlabs/httpbin
|
||||||
|
|
||||||
|
$ docker pull kennethreitz/httpbin
|
||||||
|
$ docker run -p 80:80 kennethreitz/httpbin
|
||||||
|
"""
|
||||||
|
HTTP_BIN_URL = "http://127.0.0.1:80"
|
||||||
|
|
||||||
|
|
||||||
|
def get_platform():
|
||||||
|
return {
|
||||||
|
"httprunner_version": __version__,
|
||||||
|
"python_version": "{} {}".format(
|
||||||
|
platform.python_implementation(), platform.python_version()
|
||||||
|
),
|
||||||
|
"platform": platform.platform(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def init_sentry_sdk():
|
def init_sentry_sdk():
|
||||||
if os.getenv("DISABLE_SENTRY") == "true":
|
if os.getenv("DISABLE_SENTRY") == "true":
|
||||||
return
|
return
|
||||||
@@ -30,62 +51,79 @@ def init_sentry_sdk():
|
|||||||
scope.set_user({"id": uuid.getnode()})
|
scope.set_user({"id": uuid.getnode()})
|
||||||
|
|
||||||
|
|
||||||
class GAClient(object):
|
class GA4Client(object):
|
||||||
|
"""send events to Google Analytics 4 via Measurement Protocol.
|
||||||
|
get details in hrp/internal/sdk/ga4.go
|
||||||
|
"""
|
||||||
|
|
||||||
version = "1" # GA API Version
|
def __init__(
|
||||||
report_url = "https://www.google-analytics.com/collect"
|
self, measurement_id: str, api_secret: str, debug: bool = False
|
||||||
report_debug_url = (
|
) -> None:
|
||||||
"https://www.google-analytics.com/debug/collect" # used for debug
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, tracking_id: Text):
|
|
||||||
self.http_client = requests.Session()
|
self.http_client = requests.Session()
|
||||||
self.label = f"v{__version__}"
|
|
||||||
self.common_params = {
|
self.debug = debug
|
||||||
"v": self.version,
|
if debug:
|
||||||
"tid": tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X
|
uri = "https://www.google-analytics.com/debug/mp/collect"
|
||||||
"cid": uuid.getnode(), # Anonymous Client ID
|
else:
|
||||||
"ua": f"HttpRunner/{__version__}",
|
uri = "https://www.google-analytics.com/mp/collect"
|
||||||
}
|
|
||||||
|
self.uri = f"{uri}?measurement_id={measurement_id}&api_secret={api_secret}"
|
||||||
|
self.user_id = str(uuid.getnode())
|
||||||
|
self.common_event_params = get_platform()
|
||||||
|
|
||||||
# do not send GA events in CI environment
|
# do not send GA events in CI environment
|
||||||
self.__is_ci = os.getenv("DISABLE_GA") == "true"
|
self.__is_ci = os.getenv("DISABLE_GA") == "true"
|
||||||
|
|
||||||
def track_event(self, category: Text, action: Text, value: int = 0):
|
def send_event(self, name: str, event_params: dict = None) -> None:
|
||||||
if self.__is_ci:
|
if self.__is_ci:
|
||||||
return
|
return
|
||||||
|
|
||||||
data = {
|
event_params = event_params or {}
|
||||||
"t": "event", # Event hit type = event
|
event_params.update(self.common_event_params)
|
||||||
"ec": category, # Required. Event Category.
|
event = {
|
||||||
"ea": action, # Required. Event Action.
|
"name": name,
|
||||||
"el": self.label, # Optional. Event label, used as version.
|
"params": event_params,
|
||||||
"ev": value, # Optional. Event value, must be non-negative integer
|
|
||||||
}
|
}
|
||||||
data.update(self.common_params)
|
|
||||||
try:
|
|
||||||
self.http_client.post(self.report_url, data=data, timeout=5)
|
|
||||||
except Exception: # ProxyError, SSLError, ConnectionError
|
|
||||||
pass
|
|
||||||
|
|
||||||
def track_user_timing(self, category: Text, variable: Text, duration: int):
|
payload = {
|
||||||
if self.__is_ci:
|
"client_id": f"{int(random.random() * 10**8)}.{int(time.time())}",
|
||||||
|
"user_id": self.user_id,
|
||||||
|
"timestamp_micros": int(time.time() * 10**6),
|
||||||
|
"events": [event],
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
logger.debug(f"send GA4 event, uri: {self.uri}, payload: {payload}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = self.http_client.post(self.uri, json=payload, timeout=5)
|
||||||
|
except Exception as err: # ProxyError, SSLError, ConnectionError
|
||||||
|
logger.error(f"request GA4 failed, error: {err}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if resp.status_code >= 300:
|
||||||
|
logger.error(
|
||||||
|
f"validation response got unexpected status: {resp.status_code}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.debug:
|
||||||
return
|
return
|
||||||
|
|
||||||
data = {
|
|
||||||
"t": "timing", # Event hit type = timing
|
|
||||||
"utc": category, # Required. user timing category. e.g. jsonLoader
|
|
||||||
"utv": variable, # Required. timing variable. e.g. load
|
|
||||||
"utt": duration, # Required. time took duration.
|
|
||||||
"utl": self.label, # Optional. user timing label, used as version.
|
|
||||||
}
|
|
||||||
data.update(self.common_params)
|
|
||||||
try:
|
try:
|
||||||
self.http_client.post(self.report_url, data=data, timeout=5)
|
resp_body = resp.json()
|
||||||
except Exception: # ProxyError, SSLError, ConnectionError
|
logger.debug(
|
||||||
|
"get GA4 validation response, "
|
||||||
|
f"status code: {resp.status_code}, body: {resp_body}"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
ga_client = GAClient("UA-114587036-1")
|
GA4_MEASUREMENT_ID = "G-9KHR3VC2LN"
|
||||||
|
GA4_API_SECRET = "w7lKNQIrQsKNS4ikgMPp0Q"
|
||||||
|
|
||||||
|
ga4_client = GA4Client(GA4_MEASUREMENT_ID, GA4_API_SECRET, False)
|
||||||
|
|
||||||
|
|
||||||
def set_os_environ(variables_mapping):
|
def set_os_environ(variables_mapping):
|
||||||
@@ -219,16 +257,6 @@ def omit_long_data(body, omit_len=512):
|
|||||||
return omitted_body + appendix_str
|
return omitted_body + appendix_str
|
||||||
|
|
||||||
|
|
||||||
def get_platform():
|
|
||||||
return {
|
|
||||||
"httprunner_version": __version__,
|
|
||||||
"python_version": "{} {}".format(
|
|
||||||
platform.python_implementation(), platform.python_version()
|
|
||||||
),
|
|
||||||
"platform": platform.platform(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def sort_dict_by_custom_order(raw_dict: Dict, custom_order: List):
|
def sort_dict_by_custom_order(raw_dict: Dict, custom_order: List):
|
||||||
def get_index_from_list(lst: List, item: Any):
|
def get_index_from_list(lst: List, item: Any):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
|||||||
import toml
|
import toml
|
||||||
|
|
||||||
from httprunner import __version__, loader, utils
|
from httprunner import __version__, loader, utils
|
||||||
from httprunner.utils import ExtendJSONEncoder, merge_variables
|
from httprunner.utils import ExtendJSONEncoder, merge_variables, ga4_client
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(unittest.TestCase):
|
class TestUtils(unittest.TestCase):
|
||||||
@@ -121,10 +121,10 @@ class TestUtils(unittest.TestCase):
|
|||||||
|
|
||||||
def test_override_config_variables(self):
|
def test_override_config_variables(self):
|
||||||
step_variables = {"base_url": "$base_url", "foo1": "bar1"}
|
step_variables = {"base_url": "$base_url", "foo1": "bar1"}
|
||||||
config_variables = {"base_url": "https://httpbin.org", "foo1": "bar111"}
|
config_variables = {"base_url": "https://postman-echo.com", "foo1": "bar111"}
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
merge_variables(step_variables, config_variables),
|
merge_variables(step_variables, config_variables),
|
||||||
{"base_url": "https://httpbin.org", "foo1": "bar1"},
|
{"base_url": "https://postman-echo.com", "foo1": "bar1"},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_cartesian_product_one(self):
|
def test_cartesian_product_one(self):
|
||||||
@@ -160,3 +160,12 @@ class TestUtils(unittest.TestCase):
|
|||||||
pyproject = toml.loads(open(str(path)).read())
|
pyproject = toml.loads(open(str(path)).read())
|
||||||
pyproject_version = pyproject["tool"]["poetry"]["version"]
|
pyproject_version = pyproject["tool"]["poetry"]["version"]
|
||||||
self.assertEqual(pyproject_version, __version__)
|
self.assertEqual(pyproject_version, __version__)
|
||||||
|
|
||||||
|
def test_ga4_send_event(self):
|
||||||
|
ga4_client.send_event(
|
||||||
|
"httprunner_debug_event",
|
||||||
|
{
|
||||||
|
"a": 123,
|
||||||
|
"b": 456,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|||||||
2307
poetry.lock
generated
2307
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "httprunner"
|
name = "httprunner"
|
||||||
version = "v4.3.0"
|
version = "v4.3.5"
|
||||||
description = "One-stop solution for HTTP(S) testing."
|
description = "One-stop solution for HTTP(S) testing."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -30,8 +30,6 @@ include = ["docs/CHANGELOG.md"]
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7"
|
python = "^3.7"
|
||||||
requests = "^2.22.0"
|
|
||||||
pyyaml = "^5.4.1"
|
|
||||||
pydantic = "~1.8" # >=1.8.0 <1.9.0
|
pydantic = "~1.8" # >=1.8.0 <1.9.0
|
||||||
loguru = "^0.4.1"
|
loguru = "^0.4.1"
|
||||||
jmespath = "^0.9.5"
|
jmespath = "^0.9.5"
|
||||||
@@ -40,7 +38,7 @@ pytest = "^7.1.1"
|
|||||||
pytest-html = "^3.1.1"
|
pytest-html = "^3.1.1"
|
||||||
sentry-sdk = "^0.14.4"
|
sentry-sdk = "^0.14.4"
|
||||||
allure-pytest = {version = "^2.8.16", optional = true}
|
allure-pytest = {version = "^2.8.16", optional = true}
|
||||||
requests-toolbelt = {version = "^0.9.1", optional = true}
|
requests-toolbelt = {version = "^0.10.1", optional = true}
|
||||||
filetype = {version = "^1.0.7", optional = true}
|
filetype = {version = "^1.0.7", optional = true}
|
||||||
Brotli = "^1.0.9"
|
Brotli = "^1.0.9"
|
||||||
jinja2 = "^3.0.3"
|
jinja2 = "^3.0.3"
|
||||||
@@ -50,6 +48,9 @@ pymysql = {version = "^1.0.2",optional = true}
|
|||||||
cython = {version = "^0.29.28", optional = true}
|
cython = {version = "^0.29.28", optional = true}
|
||||||
thriftpy2 = {version = "^0.4.14", optional = true}
|
thriftpy2 = {version = "^0.4.14", optional = true}
|
||||||
thrift = {version = "^0.16.0", optional = true}
|
thrift = {version = "^0.16.0", optional = true}
|
||||||
|
pyyaml = "^6.0.1"
|
||||||
|
requests = "^2.31.0"
|
||||||
|
urllib3 = "^1.26"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
allure = ["allure-pytest"] # pip install "httprunner[allure]", poetry install -E allure
|
allure = ["allure-pytest"] # pip install "httprunner[allure]", poetry install -E allure
|
||||||
|
|||||||
Reference in New Issue
Block a user