Repository: httprunner/httprunner (language: python)

Issue:
add meta info for scaffold project
## 期望的功能特性

在使用脚手架创建项目时，保存相关元信息：

- 项目名称
- 创建时间
- hrp 版本

存储到项目根目录的 `proj.json` 中。

## 示例描述

```json
{
  "projectName": "demo",
  "createTime": "Thu Feb 24 2022 09:59:51 GMT+0800 (中国标准时间)",
  "version": "4.0.0-beta"
}
```



Discussion / hints:

Fix patch:
diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md
index 2620d07fb..097cf3fbb 100644
--- a/docs/cmd/hrp.md
+++ b/docs/cmd/hrp.md
@@ -36,4 +36,4 @@ Copyright 2017 debugtalk
 * [hrp run](hrp_run.md)	 - run API test with go engine
 * [hrp startproject](hrp_startproject.md)	 - create a scaffold project
 
-###### Auto generated by spf13/cobra on 9-May-2022
+###### Auto generated by spf13/cobra on 20-May-2022
diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md
index ad27f7b27..7277436c3 100644
--- a/docs/cmd/hrp_boom.md
+++ b/docs/cmd/hrp_boom.md
@@ -31,6 +31,7 @@ hrp boom [flags]
       --max-rps int                     Max RPS that boomer can generate, disabled by default.
       --mem-profile string              Enable memory profiling.
       --mem-profile-duration duration   Memory profile duration. (default 30s)
+      --profile string                  profile for load testing
       --prometheus-gateway string       Prometheus Pushgateway url.
       --request-increase-rate string    Request increase rate, disabled by default. (default "-1")
       --spawn-count int                 The number of users to spawn for load testing (default 1)
@@ -41,4 +42,4 @@ hrp boom [flags]
 
 * [hrp](hrp.md)	 - Next-Generation API Testing Solution.
 
-###### Auto generated by spf13/cobra on 9-May-2022
+###### Auto generated by spf13/cobra on 20-May-2022
diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md
index 7390e9cc8..3b55f0355 100644
--- a/docs/cmd/hrp_convert.md
+++ b/docs/cmd/hrp_convert.md
@@ -18,4 +18,4 @@ hrp convert $path... [flags]
 
 * [hrp](hrp.md)	 - Next-Generation API Testing Solution.
 
-###### Auto generated by spf13/cobra on 9-May-2022
+###### Auto generated by spf13/cobra on 20-May-2022
diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md
index db6b8b10d..41e467877 100644
--- a/docs/cmd/hrp_har2case.md
+++ b/docs/cmd/hrp_har2case.md
@@ -24,4 +24,4 @@ hrp har2case $har_path... [flags]
 
 * [hrp](hrp.md)	 - Next-Generation API Testing Solution.
 
-###### Auto generated by spf13/cobra on 9-May-2022
+###### Auto generated by spf13/cobra on 20-May-2022
diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md
index 6ffdd6d2c..8a1fc59b3 100644
--- a/docs/cmd/hrp_run.md
+++ b/docs/cmd/hrp_run.md
@@ -35,4 +35,4 @@ hrp run $path... [flags]
 
 * [hrp](hrp.md)	 - Next-Generation API Testing Solution.
 
-###### Auto generated by spf13/cobra on 9-May-2022
+###### Auto generated by spf13/cobra on 20-May-2022
diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md
index 4987cd6dc..b669b71d2 100644
--- a/docs/cmd/hrp_startproject.md
+++ b/docs/cmd/hrp_startproject.md
@@ -20,4 +20,4 @@ hrp startproject $project_name [flags]
 
 * [hrp](hrp.md)	 - Next-Generation API Testing Solution.
 
-###### Auto generated by spf13/cobra on 9-May-2022
+###### Auto generated by spf13/cobra on 20-May-2022
diff --git a/hrp/cmd/boom.go b/hrp/cmd/boom.go
index 22d8782d5..5ae038e1f 100644
--- a/hrp/cmd/boom.go
+++ b/hrp/cmd/boom.go
@@ -1,12 +1,15 @@
 package cmd
 
 import (
+	"os"
 	"time"
 
+	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
 
 	"github.com/httprunner/httprunner/v4/hrp"
 	"github.com/httprunner/httprunner/v4/hrp/internal/boomer"
+	"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
 )
 
 // boomCmd represents the boom command
@@ -28,57 +31,70 @@ var boomCmd = &cobra.Command{
 			path := hrp.TestCasePath(arg)
 			paths = append(paths, &path)
 		}
-		hrpBoomer := hrp.NewBoomer(spawnCount, spawnRate)
-		hrpBoomer.SetRateLimiter(maxRPS, requestIncreaseRate)
-		if loopCount > 0 {
-			hrpBoomer.SetLoopCount(loopCount)
+		// if set profile, the priority is higher than the other commands
+		if boomArgs.profile != "" {
+			err := builtin.LoadFile(boomArgs.profile, &boomArgs)
+			if err != nil {
+				log.Error().Err(err).Msg("failed to load profile")
+				os.Exit(1)
+			}
 		}
-		if !disableConsoleOutput {
+
+		hrpBoomer := hrp.NewBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate)
+		hrpBoomer.SetRateLimiter(boomArgs.MaxRPS, boomArgs.RequestIncreaseRate)
+		if boomArgs.LoopCount > 0 {
+			hrpBoomer.SetLoopCount(boomArgs.LoopCount)
+		}
+		if !boomArgs.DisableConsoleOutput {
 			hrpBoomer.AddOutput(boomer.NewConsoleOutput())
 		}
-		if prometheusPushgatewayURL != "" {
-			hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(prometheusPushgatewayURL, "hrp", hrpBoomer.GetMode()))
+		if boomArgs.PrometheusPushgatewayURL != "" {
+			hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(boomArgs.PrometheusPushgatewayURL, "hrp", hrpBoomer.GetMode()))
 		}
-		hrpBoomer.SetDisableKeepAlive(disableKeepalive)
-		hrpBoomer.SetDisableCompression(disableCompression)
+		hrpBoomer.SetDisableKeepAlive(boomArgs.DisableKeepalive)
+		hrpBoomer.SetDisableCompression(boomArgs.DisableCompression)
 		hrpBoomer.SetClientTransport()
-		hrpBoomer.EnableCPUProfile(cpuProfile, cpuProfileDuration)
-		hrpBoomer.EnableMemoryProfile(memoryProfile, memoryProfileDuration)
+		hrpBoomer.EnableCPUProfile(boomArgs.CPUProfile, boomArgs.CPUProfileDuration)
+		hrpBoomer.EnableMemoryProfile(boomArgs.MemoryProfile, boomArgs.MemoryProfileDuration)
 		hrpBoomer.EnableGracefulQuit()
 		hrpBoomer.Run(paths...)
 	},
 }
 
-var (
-	spawnCount               int
-	spawnRate                float64
-	maxRPS                   int64
-	loopCount                int64
-	requestIncreaseRate      string
-	memoryProfile            string
-	memoryProfileDuration    time.Duration
-	cpuProfile               string
-	cpuProfileDuration       time.Duration
-	prometheusPushgatewayURL string
-	disableConsoleOutput     bool
-	disableCompression       bool
-	disableKeepalive         bool
-)
+type BoomArgs struct {
+	SpawnCount               int           `json:"spawn-count,omitempty" yaml:"spawn-count,omitempty"`
+	SpawnRate                float64       `json:"spawn-rate,omitempty" yaml:"spawn-rate,omitempty"`
+	MaxRPS                   int64         `json:"max-rps,omitempty" yaml:"max-rps,omitempty"`
+	LoopCount                int64         `json:"loop-count,omitempty" yaml:"loop-count,omitempty"`
+	RequestIncreaseRate      string        `json:"request-increase-rate,omitempty" yaml:"request-increase-rate,omitempty"`
+	MemoryProfile            string        `json:"memory-profile,omitempty" yaml:"memory-profile,omitempty"`
+	MemoryProfileDuration    time.Duration `json:"memory-profile-duration" yaml:"memory-profile-duration"`
+	CPUProfile               string        `json:"cpu-profile,omitempty" yaml:"cpu-profile,omitempty"`
+	CPUProfileDuration       time.Duration `json:"cpu-profile-duration,omitempty" yaml:"cpu-profile-duration,omitempty"`
+	PrometheusPushgatewayURL string        `json:"prometheus-gateway,omitempty" yaml:"prometheus-gateway,omitempty"`
+	DisableConsoleOutput     bool          `json:"disable-console-output,omitempty" yaml:"disable-console-output,omitempty"`
+	DisableCompression       bool          `json:"disable-compression,omitempty" yaml:"disable-compression,omitempty"`
+	DisableKeepalive         bool          `json:"disable-keepalive,omitempty" yaml:"disable-keepalive,omitempty"`
+	profile                  string
+}
+
+var boomArgs BoomArgs
 
 func init() {
 	rootCmd.AddCommand(boomCmd)
 
-	boomCmd.Flags().Int64Var(&maxRPS, "max-rps", 0, "Max RPS that boomer can generate, disabled by default.")
-	boomCmd.Flags().StringVar(&requestIncreaseRate, "request-increase-rate", "-1", "Request increase rate, disabled by default.")
-	boomCmd.Flags().IntVar(&spawnCount, "spawn-count", 1, "The number of users to spawn for load testing")
-	boomCmd.Flags().Float64Var(&spawnRate, "spawn-rate", 1, "The rate for spawning users")
-	boomCmd.Flags().Int64Var(&loopCount, "loop-count", -1, "The specify running cycles for load testing")
-	boomCmd.Flags().StringVar(&memoryProfile, "mem-profile", "", "Enable memory profiling.")
-	boomCmd.Flags().DurationVar(&memoryProfileDuration, "mem-profile-duration", 30*time.Second, "Memory profile duration.")
-	boomCmd.Flags().StringVar(&cpuProfile, "cpu-profile", "", "Enable CPU profiling.")
-	boomCmd.Flags().DurationVar(&cpuProfileDuration, "cpu-profile-duration", 30*time.Second, "CPU profile duration.")
-	boomCmd.Flags().StringVar(&prometheusPushgatewayURL, "prometheus-gateway", "", "Prometheus Pushgateway url.")
-	boomCmd.Flags().BoolVar(&disableConsoleOutput, "disable-console-output", false, "Disable console output.")
-	boomCmd.Flags().BoolVar(&disableCompression, "disable-compression", false, "Disable compression")
-	boomCmd.Flags().BoolVar(&disableKeepalive, "disable-keepalive", false, "Disable keepalive")
+	boomCmd.Flags().Int64Var(&boomArgs.MaxRPS, "max-rps", 0, "Max RPS that boomer can generate, disabled by default.")
+	boomCmd.Flags().StringVar(&boomArgs.RequestIncreaseRate, "request-increase-rate", "-1", "Request increase rate, disabled by default.")
+	boomCmd.Flags().IntVar(&boomArgs.SpawnCount, "spawn-count", 1, "The number of users to spawn for load testing")
+	boomCmd.Flags().Float64Var(&boomArgs.SpawnRate, "spawn-rate", 1, "The rate for spawning users")
+	boomCmd.Flags().Int64Var(&boomArgs.LoopCount, "loop-count", -1, "The specify running cycles for load testing")
+	boomCmd.Flags().StringVar(&boomArgs.MemoryProfile, "mem-profile", "", "Enable memory profiling.")
+	boomCmd.Flags().DurationVar(&boomArgs.MemoryProfileDuration, "mem-profile-duration", 30*time.Second, "Memory profile duration.")
+	boomCmd.Flags().StringVar(&boomArgs.CPUProfile, "cpu-profile", "", "Enable CPU profiling.")
+	boomCmd.Flags().DurationVar(&boomArgs.CPUProfileDuration, "cpu-profile-duration", 30*time.Second, "CPU profile duration.")
+	boomCmd.Flags().StringVar(&boomArgs.PrometheusPushgatewayURL, "prometheus-gateway", "", "Prometheus Pushgateway url.")
+	boomCmd.Flags().BoolVar(&boomArgs.DisableConsoleOutput, "disable-console-output", false, "Disable console output.")
+	boomCmd.Flags().BoolVar(&boomArgs.DisableCompression, "disable-compression", false, "Disable compression")
+	boomCmd.Flags().BoolVar(&boomArgs.DisableKeepalive, "disable-keepalive", false, "Disable keepalive")
+	boomCmd.Flags().StringVar(&boomArgs.profile, "profile", "", "profile for load testing")
 }
diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go
index ca505d68a..a0fe5efaa 100644
--- a/hrp/internal/scaffold/main.go
+++ b/hrp/internal/scaffold/main.go
@@ -6,6 +6,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"time"
 
 	"github.com/httprunner/funplugin/shared"
 	"github.com/pkg/errors"
@@ -13,6 +14,7 @@ import (
 
 	"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
 	"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
+	"github.com/httprunner/httprunner/v4/hrp/internal/version"
 )
 
 type PluginType string
@@ -23,6 +25,13 @@ const (
 	Go     PluginType = "go"
 )
 
+type ProjectInfo struct {
+	ProjectName string    `json:"project_name,omitempty" yaml:"project_name,omitempty"`
+	ProjectPath string    `json:"project_path,omitempty" yaml:"project_path,omitempty"`
+	CreateTime  time.Time `json:"create_time,omitempty" yaml:"create_time,omitempty"`
+	Version     string    `json:"hrp_version,omitempty" yaml:"hrp_version,omitempty"`
+}
+
 //go:embed templates/*
 var templatesDir embed.FS
 
@@ -68,6 +77,12 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error
 		os.RemoveAll(projectName)
 	}
 
+	// get project abs path
+	projectPath, err := filepath.Abs(projectName)
+	if err != nil {
+		projectPath = projectName
+	}
+
 	// create project folders
 	if err := builtin.CreateFolder(projectName); err != nil {
 		return err
@@ -88,8 +103,21 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error
 		return err
 	}
 
+	projectInfo := &ProjectInfo{
+		ProjectName: projectName,
+		ProjectPath: projectPath,
+		CreateTime:  time.Now(),
+		Version:     version.VERSION,
+	}
+
+	// dump project information to file
+	err = builtin.Dump2JSON(projectInfo, filepath.Join(projectName, "proj.json"))
+	if err != nil {
+		return err
+	}
+
 	// create .gitignore
-	err := CopyFile("templates/gitignore", filepath.Join(projectName, ".gitignore"))
+	err = CopyFile("templates/gitignore", filepath.Join(projectName, ".gitignore"))
 	if err != nil {
 		return err
 	}
diff --git a/hrp/parameters.go b/hrp/parameters.go
index 5797f6187..376232fa0 100644
--- a/hrp/parameters.go
+++ b/hrp/parameters.go
@@ -12,17 +12,17 @@ import (
 )
 
 type TParamsConfig struct {
-	Strategy   iteratorStrategy            `json:"strategy,omitempty" yaml:"strategy,omitempty"`     // overall strategy
+	PickOrder  iteratorPickOrder           `json:"strategy,omitempty" yaml:"strategy,omitempty"`     // overall pick-order strategy
 	Strategies map[string]iteratorStrategy `json:"strategies,omitempty" yaml:"strategies,omitempty"` // individual strategies for each parameters
 	Limit      int                         `json:"limit,omitempty" yaml:"limit,omitempty"`
 }
 
-type iteratorStrategy string
+type iteratorPickOrder string
 
 const (
-	strategySequential iteratorStrategy = "sequential"
-	strategyRandom     iteratorStrategy = "random"
-	strategyUnique     iteratorStrategy = "unique"
+	pickOrderSequential iteratorPickOrder = "sequential"
+	pickOrderRandom     iteratorPickOrder = "random"
+	pickOrderUnique     iteratorPickOrder = "unique"
 )
 
 /*
@@ -33,6 +33,11 @@ const (
 */
 type Parameters []map[string]interface{}
 
+type iteratorStrategy struct {
+	Name      string            `json:"name,omitempty" yaml:"name,omitempty"`
+	PickOrder iteratorPickOrder `json:"pick_order,omitempty" yaml:"pick_order,omitempty"`
+}
+
 func initParametersIterator(cfg *TConfig) (*ParametersIterator, error) {
 	parameters, err := loadParameters(cfg.Parameters, cfg.Variables)
 	if err != nil {
@@ -62,15 +67,15 @@ func newParametersIterator(parameters map[string]Parameters, config *TParamsConf
 
 	parametersList := make([]Parameters, 0)
 	for paramName := range parameters {
-		// check parameter individual strategy
+		// check parameter individual pick order strategy
 		strategy, ok := config.Strategies[paramName]
 		if !ok {
-			// default to overall strategy
-			strategy = config.Strategy
+			// default to overall pick order strategy
+			strategy.PickOrder = config.PickOrder
 		}
 
-		// group parameters by strategy
-		if strategy == strategyRandom {
+		// group parameters by pick order strategy
+		if strategy.PickOrder == pickOrderRandom {
 			iterator.randomParameterNames = append(iterator.randomParameterNames, paramName)
 		} else {
 			parametersList = append(parametersList, parameters[paramName])
diff --git a/hrp/plugin.go b/hrp/plugin.go
index 7929d0308..ab2f34e6b 100644
--- a/hrp/plugin.go
+++ b/hrp/plugin.go
@@ -118,7 +118,7 @@ func locateFile(startPath string, destFile string) (string, error) {
 	return locateFile(parentDir, destFile)
 }
 
-func getProjectRootDirPath(path string) (rootDir string, err error) {
+func GetProjectRootDirPath(path string) (rootDir string, err error) {
 	pluginPath, err := locatePlugin(path)
 	if err == nil {
 		rootDir = filepath.Dir(pluginPath)


Test patch:
diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md
index b2217ca18..0bdbab3cd 100644
--- a/docs/cmd/hrp_pytest.md
+++ b/docs/cmd/hrp_pytest.md
@@ -16,4 +16,4 @@ hrp pytest $path ... [flags]
 
 * [hrp](hrp.md)	 - Next-Generation API Testing Solution.
 
-###### Auto generated by spf13/cobra on 9-May-2022
+###### Auto generated by spf13/cobra on 20-May-2022
diff --git a/examples/hrp/parameters_test.json b/examples/hrp/parameters_test.json
index 84c36531a..9944599ef 100644
--- a/examples/hrp/parameters_test.json
+++ b/examples/hrp/parameters_test.json
@@ -10,8 +10,14 @@
     },
     "parameters_setting": {
       "strategies": {
-        "user_agent": "sequential",
-        "username-password": "random"
+        "user_agent": {
+          "name": "user-identity",
+          "pick_order": "sequential"
+        },
+        "username-password": {
+          "name": "user-info",
+          "pick_order": "random"
+        }
       },
       "limit": 6
     },
diff --git a/examples/hrp/parameters_test.yaml b/examples/hrp/parameters_test.yaml
index b5d06c71d..aaaf9b414 100644
--- a/examples/hrp/parameters_test.yaml
+++ b/examples/hrp/parameters_test.yaml
@@ -5,8 +5,12 @@ config:
     username-password: ${parameterize($file)}
   parameters_setting:
     strategies:
-      user_agent: "sequential"
-      username-password: "random"
+      user_agent:
+        name: "user-identity"
+        pick_order: "sequential"
+      username-password:
+        name: "user-info"
+        pick_order: "random"
     limit: 6
   variables:
     app_version: v1
diff --git a/hrp/parameters_test.go b/hrp/parameters_test.go
index 19bb9faeb..021764155 100644
--- a/hrp/parameters_test.go
+++ b/hrp/parameters_test.go
@@ -137,25 +137,25 @@ func TestInitParametersIteratorCount(t *testing.T) {
 			},
 			6, // 3 * 2 * 1
 		},
-		// default equals to set overall parameters strategy to "sequential"
+		// default equals to set overall parameters pick-order to "sequential"
 		{
 			&TConfig{
 				Parameters: configParameters,
 				ParametersSetting: &TParamsConfig{
-					Strategy: "sequential",
+					PickOrder: "sequential",
 				},
 			},
 			6, // 3 * 2 * 1
 		},
-		// default equals to set each individual parameters strategy to "sequential"
+		// default equals to set each individual parameters pick-order to "sequential"
 		{
 			&TConfig{
 				Parameters: configParameters,
 				ParametersSetting: &TParamsConfig{
 					Strategies: map[string]iteratorStrategy{
-						"username-password": "sequential",
-						"user_agent":        "sequential",
-						"app_version":       "sequential",
+						"username-password": {Name: "user-info", PickOrder: "sequential"},
+						"user_agent":        {Name: "user-identity", PickOrder: "sequential"},
+						"app_version":       {Name: "app-version", PickOrder: "sequential"},
 					},
 				},
 			},
@@ -166,33 +166,33 @@ func TestInitParametersIteratorCount(t *testing.T) {
 				Parameters: configParameters,
 				ParametersSetting: &TParamsConfig{
 					Strategies: map[string]iteratorStrategy{
-						"user_agent":  "sequential",
-						"app_version": "sequential",
+						"user_agent":  {Name: "user-identity", PickOrder: "sequential"},
+						"app_version": {Name: "app-version", PickOrder: "sequential"},
 					},
 				},
 			},
 			6, // 3 * 2 * 1
 		},
 
-		// set overall parameters overall strategy to "random"
+		// set overall parameters overall pick-order to "random"
 		// each random parameters only select one item
 		{
 			&TConfig{
 				Parameters: configParameters,
 				ParametersSetting: &TParamsConfig{
-					Strategy: "random",
+					PickOrder: "random",
 				},
 			},
 			1, // 1 * 1 * 1
 		},
-		// set some individual parameters strategy to "random"
+		// set some individual parameters pick-order to "random"
 		// this will override overall strategy
 		{
 			&TConfig{
 				Parameters: configParameters,
 				ParametersSetting: &TParamsConfig{
 					Strategies: map[string]iteratorStrategy{
-						"user_agent": "random",
+						"user_agent": {Name: "user-identity", PickOrder: "random"},
 					},
 				},
 			},
@@ -203,7 +203,7 @@ func TestInitParametersIteratorCount(t *testing.T) {
 				Parameters: configParameters,
 				ParametersSetting: &TParamsConfig{
 					Strategies: map[string]iteratorStrategy{
-						"username-password": "random",
+						"username-password": {Name: "user-info", PickOrder: "random"},
 					},
 				},
 			},
@@ -349,7 +349,7 @@ func TestInitParametersIteratorContent(t *testing.T) {
 				ParametersSetting: &TParamsConfig{
 					Limit: 5, // limit could also be greater than total
 					Strategies: map[string]iteratorStrategy{
-						"username-password": "random",
+						"username-password": {Name: "user-info", PickOrder: "random"},
 					},
 				},
 			},
diff --git a/hrp/runner_test.go b/hrp/runner_test.go
index 2d743e6c7..d03f8d75f 100644
--- a/hrp/runner_test.go
+++ b/hrp/runner_test.go
@@ -207,7 +207,7 @@ func TestLoadTestCases(t *testing.T) {
 	if !assert.Nil(t, err) {
 		t.Fatal()
 	}
-	if !assert.Equal(t, len(testCases), 4) {
+	if !assert.Equal(t, 4, len(testCases)) {
 		t.Fatal()
 	}
 
diff --git a/hrp/testcase.go b/hrp/testcase.go
index 05c2b0519..2a4e589c7 100644
--- a/hrp/testcase.go
+++ b/hrp/testcase.go
@@ -75,7 +75,7 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) {
 	}
 
 	// locate project root dir by plugin path
-	projectRootDir, err := getProjectRootDirPath(casePath)
+	projectRootDir, err := GetProjectRootDirPath(casePath)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to get project root dir")
 	}
@@ -287,8 +287,8 @@ func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) {
 			testCasePath := TestCasePath(path)
 			tc, err := testCasePath.ToTestCase()
 			if err != nil {
-				log.Error().Err(err).Str("path", path).Msg("load testcase failed")
-				return errors.Wrap(err, "load testcase failed")
+				log.Warn().Err(err).Str("path", path).Msg("load testcase failed")
+				return nil
 			}
 			testCases = append(testCases, tc)
 			return nil