mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 18:22:24 +08:00
252 lines
7.2 KiB
Go
252 lines
7.2 KiB
Go
package cypress
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/e2e/internal/fpaths"
|
|
"github.com/grafana/grafana/e2e/internal/outs"
|
|
"github.com/urfave/cli/v3"
|
|
)
|
|
|
|
func NewCmd() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "cypress",
|
|
Usage: "Run a Cypress test suite",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "command",
|
|
Usage: "Cypress command to run. 'open' can be useful for development (enum: run, open)",
|
|
Value: "run",
|
|
Validator: func(s string) error {
|
|
if s != "run" && s != "open" {
|
|
return fmt.Errorf("invalid command: %s, must be 'run' or 'open'", s)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "browser",
|
|
Usage: "Browser to run tests with (e.g.: chrome, electron)",
|
|
Value: "chrome",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "grafana-base-url",
|
|
Usage: "Base URL for Grafana",
|
|
Value: "http://localhost:3001",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "cypress-video",
|
|
Usage: "Enable Cypress video recordings",
|
|
Value: false,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "smtp-plugin",
|
|
Usage: "Enable SMTP plugin",
|
|
Value: false,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "benchmark-plugin",
|
|
Usage: "Enable Benchmark plugin",
|
|
Value: false,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "slowmo",
|
|
Usage: "Slow down the test run",
|
|
Value: false,
|
|
},
|
|
&cli.StringSliceFlag{
|
|
Name: "env",
|
|
Usage: "Additional Cypress environment variables to set (format: KEY=VALUE)",
|
|
Validator: func(s []string) error {
|
|
pattern := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*=.*`)
|
|
for _, v := range s {
|
|
if !pattern.MatchString(v) {
|
|
return fmt.Errorf("invalid environment variable format: %s, must be KEY=VALUE", v)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
&cli.StringSliceFlag{
|
|
Name: "parameters",
|
|
Usage: "Additional parameters to pass to the Cypress command (e.g. --headed)",
|
|
},
|
|
&cli.DurationFlag{
|
|
Name: "timeout",
|
|
Usage: "Timeout for the Cypress command (precision: milliseconds)",
|
|
Value: time.Second * 30,
|
|
Validator: func(d time.Duration) error {
|
|
if d < 0 {
|
|
return fmt.Errorf("timeout must be a positive duration")
|
|
}
|
|
if d.Round(time.Millisecond) != d {
|
|
return fmt.Errorf("timeout must be a whole number of milliseconds")
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
|
|
&cli.BoolFlag{
|
|
Name: "start-grafana",
|
|
Usage: "Start and wait for Grafana before running the tests",
|
|
Value: true,
|
|
Category: "Grafana Server",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "license-path",
|
|
Usage: "Path to the Grafana Enterprise license file (optional; requires --start-grafana)",
|
|
Value: "",
|
|
TakesFile: true,
|
|
Category: "Grafana Server",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "image-renderer",
|
|
Usage: "Install the image renderer plugin (requires --start-grafana)",
|
|
Category: "Grafana Server",
|
|
},
|
|
|
|
&cli.StringFlag{
|
|
Name: "suite",
|
|
Usage: "Path to the suite to run (e.g. './e2e/dashboards-suite')",
|
|
TakesFile: true,
|
|
Required: true,
|
|
},
|
|
},
|
|
Action: runAction,
|
|
}
|
|
}
|
|
|
|
func runAction(ctx context.Context, c *cli.Command) error {
|
|
suitePath := c.String("suite")
|
|
suitePath, err := fpaths.NormalisePath(suitePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to normalise suite path: %w", err)
|
|
}
|
|
|
|
repoRoot, err := fpaths.RepoRoot(ctx, suitePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get git repo root: %w", err)
|
|
}
|
|
|
|
screenshotsFolder := path.Join(suitePath, "screenshots")
|
|
videosFolder := path.Join(suitePath, "videos")
|
|
fileServerFolder := path.Join(repoRoot, "e2e", "cypress")
|
|
fixturesFolder := path.Join(fileServerFolder, "fixtures")
|
|
downloadsFolder := path.Join(fileServerFolder, "downloads")
|
|
benchmarkPluginResultsFolder := path.Join(suitePath, "benchmark-results")
|
|
reporter := path.Join(repoRoot, "e2e", "log-reporter.js")
|
|
|
|
env := map[string]string{
|
|
"BENCHMARK_PLUGIN_ENABLED": fmt.Sprintf("%t", c.Bool("benchmark-plugin")),
|
|
"SMTP_PLUGIN_ENABLED": fmt.Sprintf("%t", c.Bool("smtp-plugin")),
|
|
"BENCHMARK_PLUGIN_RESULTS_FOLDER": benchmarkPluginResultsFolder,
|
|
"SLOWMO": "0",
|
|
"BASE_URL": c.String("grafana-base-url"),
|
|
}
|
|
for _, v := range c.StringSlice("env") {
|
|
parts := strings.SplitN(v, "=", 2)
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("invalid environment variable format: %s, must be KEY=VALUE", v)
|
|
}
|
|
env[parts[0]] = parts[1]
|
|
}
|
|
|
|
cypressConfig := map[string]string{
|
|
"screenshotsFolder": screenshotsFolder,
|
|
"fixturesFolder": fixturesFolder,
|
|
"videosFolder": videosFolder,
|
|
"downloadsFolder": downloadsFolder,
|
|
"fileServerFolder": fileServerFolder,
|
|
"reporter": reporter,
|
|
|
|
"specPattern": path.Join(suitePath, "*.spec.ts"),
|
|
"defaultCommandTimeout": fmt.Sprintf("%d", c.Duration("timeout").Milliseconds()),
|
|
"viewportWidth": "1920",
|
|
"viewportHeight": "1080",
|
|
"trashAssetsBeforeRuns": "false",
|
|
"baseUrl": c.String("grafana-base-url"),
|
|
"video": fmt.Sprintf("%t", c.Bool("cypress-video")),
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
if c.Bool("start-grafana") {
|
|
startServerPath := path.Join(repoRoot, "scripts", "grafana-server", "start-server")
|
|
waitForGrafanaPath := path.Join(repoRoot, "scripts", "grafana-server", "wait-for-grafana")
|
|
go func() {
|
|
defer cancel()
|
|
var args []string
|
|
if c.String("license-path") != "" {
|
|
args = append(args, c.String("license-path"))
|
|
}
|
|
//nolint:gosec
|
|
cmd := exec.CommandContext(ctx, startServerPath, args...)
|
|
cmd.Dir = repoRoot
|
|
cmd.Env = os.Environ()
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("TZ=%s", c.String("timezone")))
|
|
if c.Bool("image-renderer") {
|
|
cmd.Env = append(cmd.Env, "INSTALL_IMAGE_RENDERER=true")
|
|
}
|
|
cmd.Stdout = prefixGrafana(os.Stdout)
|
|
cmd.Stderr = prefixGrafana(os.Stderr)
|
|
cmd.Stdin = nil
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
fmt.Println("Error running Grafana:", err)
|
|
}
|
|
}()
|
|
|
|
//nolint:gosec
|
|
cmd := exec.CommandContext(ctx, waitForGrafanaPath)
|
|
cmd.Dir = repoRoot
|
|
cmd.Env = os.Environ()
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("TZ=%s", c.String("timezone")))
|
|
cmd.Stdout = prefixGrafana(os.Stdout)
|
|
cmd.Stderr = prefixGrafana(os.Stderr)
|
|
cmd.Stdin = nil
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to wait for Grafana: %w", err)
|
|
}
|
|
}
|
|
|
|
args := []string{"run", "cypress", c.String("command"),
|
|
"--env", joinCypressCfg(env),
|
|
"--config", joinCypressCfg(cypressConfig),
|
|
"--browser", c.String("browser")}
|
|
args = append(args, c.StringSlice("parameters")...)
|
|
//nolint:gosec
|
|
cmd := exec.CommandContext(ctx, "yarn", args...)
|
|
cmd.Dir = repoRoot
|
|
cmd.Env = os.Environ()
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("TZ=%s", c.String("timezone")))
|
|
cmd.Stdout = prefixCypress(os.Stdout)
|
|
cmd.Stderr = prefixCypress(os.Stderr)
|
|
cmd.Stdin = os.Stdin
|
|
return cmd.Run()
|
|
}
|
|
|
|
func joinCypressCfg(cfg map[string]string) string {
|
|
config := make([]string, 0, len(cfg))
|
|
for k, v := range cfg {
|
|
config = append(config, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
return strings.Join(config, ",")
|
|
}
|
|
|
|
func prefixCypress(w io.Writer) io.Writer {
|
|
return outs.Prefix(w, "Cypress", outs.CyanColor)
|
|
}
|
|
|
|
func prefixGrafana(w io.Writer) io.Writer {
|
|
return outs.Prefix(w, "Grafana", outs.YellowColor)
|
|
}
|