github.com/saucelabs/saucectl@v0.175.1/internal/cmd/run/cypress.go (about)

     1  package run
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  
     7  	cmds "github.com/saucelabs/saucectl/internal/cmd"
     8  	"github.com/saucelabs/saucectl/internal/http"
     9  
    10  	"github.com/rs/zerolog/log"
    11  	"github.com/spf13/cobra"
    12  	"github.com/spf13/pflag"
    13  	"golang.org/x/text/cases"
    14  	"golang.org/x/text/language"
    15  
    16  	"github.com/saucelabs/saucectl/internal/ci"
    17  	"github.com/saucelabs/saucectl/internal/config"
    18  	"github.com/saucelabs/saucectl/internal/cypress"
    19  	"github.com/saucelabs/saucectl/internal/flags"
    20  	"github.com/saucelabs/saucectl/internal/framework"
    21  	"github.com/saucelabs/saucectl/internal/msg"
    22  	"github.com/saucelabs/saucectl/internal/region"
    23  	"github.com/saucelabs/saucectl/internal/report/captor"
    24  	"github.com/saucelabs/saucectl/internal/saucecloud"
    25  	"github.com/saucelabs/saucectl/internal/saucecloud/retry"
    26  	"github.com/saucelabs/saucectl/internal/segment"
    27  	"github.com/saucelabs/saucectl/internal/usage"
    28  	"github.com/saucelabs/saucectl/internal/viper"
    29  )
    30  
    31  // NewCypressCmd creates the 'run' command for Cypress.
    32  func NewCypressCmd() *cobra.Command {
    33  	sc := flags.SnakeCharmer{Fmap: map[string]*pflag.Flag{}}
    34  
    35  	cmd := &cobra.Command{
    36  		Use:              "cypress",
    37  		Short:            "Run cypress tests",
    38  		SilenceUsage:     true,
    39  		Hidden:           true, // TODO reveal command once ready
    40  		TraverseChildren: true,
    41  		PreRunE: func(cmd *cobra.Command, args []string) error {
    42  			sc.BindAll()
    43  			return preRun()
    44  		},
    45  		Run: func(cmd *cobra.Command, args []string) {
    46  			// Test patterns are passed in via positional args.
    47  			viper.Set("suite::config::specPattern", args)
    48  
    49  			exitCode, err := runCypress(cmd, true)
    50  			if err != nil {
    51  				log.Err(err).Msg("failed to execute run command")
    52  			}
    53  			os.Exit(exitCode)
    54  		},
    55  	}
    56  
    57  	sc.Fset = cmd.Flags()
    58  	sc.String("name", "suite::name", "", "Set the name of the job as it will appear on Sauce Labs")
    59  
    60  	// Browser & Platform
    61  	sc.String("browser", "suite::browser", "", "Run tests against this browser")
    62  	sc.String("browserVersion", "suite::browserVersion", "", "The browser version (default: latest)")
    63  	sc.String("platform", "suite::platformName", "", "Run tests against this platform")
    64  
    65  	// Cypress
    66  	sc.String("cypress.version", "cypress::version", "", "The Cypress version to use")
    67  	sc.String("cypress.configFile", "cypress::configFile", "", "The path to the cypress.json config file")
    68  	sc.String("cypress.key", "cypress::key", "", "The Cypress record key")
    69  	sc.Bool("cypress.record", "cypress::record", false, "Whether or not to record tests to the cypress dashboard")
    70  	sc.StringSlice("excludeSpecPattern", "suite::config::excludeSpecPattern", []string{}, "Exclude test files to skip the tests, using glob pattern")
    71  	sc.String("testingType", "suite::config::testingType", "e2e", "Specify the type of tests to execute; either e2e or component. Defaults to e2e")
    72  
    73  	// Video & Screen(shots)
    74  	sc.String("screenResolution", "suite::screenResolution", "", "The screen resolution")
    75  
    76  	// Misc
    77  	sc.String("rootDir", "rootDir", ".", "Control what files are available in the context of a test run, unless explicitly excluded by .sauceignore")
    78  	sc.String("shard", "suite::shard", "", "Controls whether or not (and how) tests are sharded across multiple machines, supported value: spec|concurrency")
    79  	sc.Bool("shardGrepEnabled", "suite::shardGrepEnabled", false, "When sharding is configured and the suite is configured to filter using cypress-grep, let saucectl filter tests before executing")
    80  	sc.String("headless", "suite::headless", "", "Controls whether or not tests are run in headless mode (default: false)")
    81  	sc.String("timeZone", "suite::timeZone", "", "Specifies timeZone for this test")
    82  	sc.Int("passThreshold", "suite::passThreshold", 1, "The minimum number of successful attempts for a suite to be considered as 'passed'.")
    83  
    84  	// NPM
    85  	sc.String("npm.registry", "npm::registry", "", "Specify the npm registry URL")
    86  	sc.StringToString("npm.packages", "npm::packages", map[string]string{}, "Specify npm packages that are required to run tests")
    87  	sc.StringSlice("npm.dependencies", "npm::dependencies", []string{}, "Specify local npm dependencies for saucectl to upload. These dependencies must already be installed in the local node_modules directory.")
    88  	sc.Bool("npm.strictSSL", "npm::strictSSL", true, "Whether or not to do SSL key validation when making requests to the registry via https")
    89  
    90  	return cmd
    91  }
    92  
    93  func runCypress(cmd *cobra.Command, isCLIDriven bool) (int, error) {
    94  	if !isCLIDriven {
    95  		config.ValidateSchema(gFlags.cfgFilePath)
    96  	}
    97  
    98  	p, err := cypress.FromFile(gFlags.cfgFilePath)
    99  	if err != nil {
   100  		return 1, err
   101  	}
   102  
   103  	p.SetCLIFlags(flags.CaptureCommandLineFlags(cmd.Flags()))
   104  	if err := p.ApplyFlags(gFlags.selectedSuite); err != nil {
   105  		return 1, err
   106  	}
   107  	p.SetDefaults()
   108  	if !gFlags.noAutoTagging {
   109  		p.AppendTags(ci.GetTags())
   110  	}
   111  
   112  	if err := p.Validate(); err != nil {
   113  		return 1, err
   114  	}
   115  
   116  	regio := region.FromString(p.GetSauceCfg().Region)
   117  	if regio == region.USEast4 {
   118  		return 1, errors.New(msg.NoFrameworkSupport)
   119  	}
   120  
   121  	tracker := segment.DefaultTracker
   122  	if regio == region.Staging {
   123  		tracker.Enabled = false
   124  	}
   125  
   126  	go func() {
   127  		props := usage.Properties{}
   128  		props.SetFramework("cypress").SetFVersion(p.GetVersion()).SetFlags(cmd.Flags()).SetSauceConfig(p.GetSauceCfg()).
   129  			SetArtifacts(p.GetArtifactsCfg()).SetNPM(p.GetNpm()).SetNumSuites(len(p.GetSuites())).SetJobs(captor.Default.TestResults).
   130  			SetSlack(p.GetNotifications().Slack).SetSharding(p.IsSharded()).SetLaunchOrder(p.GetSauceCfg().LaunchOrder).
   131  			SetSmartRetry(p.IsSmartRetried()).SetReporters(p.GetReporters())
   132  
   133  		tracker.Collect(cases.Title(language.English).String(cmds.FullName(cmd)), props)
   134  		_ = tracker.Close()
   135  	}()
   136  
   137  	cleanupArtifacts(p.GetArtifactsCfg())
   138  
   139  	creds := regio.Credentials()
   140  
   141  	restoClient := http.NewResto(regio.APIBaseURL(), creds.Username, creds.AccessKey, 0)
   142  	restoClient.ArtifactConfig = p.GetArtifactsCfg().Download
   143  	testcompClient := http.NewTestComposer(regio.APIBaseURL(), creds, testComposerTimeout)
   144  	webdriverClient := http.NewWebdriver(regio.WebDriverBaseURL(), creds, webdriverTimeout)
   145  	appsClient := *http.NewAppStore(regio.APIBaseURL(), creds.Username, creds.AccessKey, gFlags.appStoreTimeout)
   146  	rdcClient := http.NewRDCService(regio.APIBaseURL(), creds.Username, creds.AccessKey, rdcTimeout, config.ArtifactDownload{})
   147  	insightsClient := http.NewInsightsService(regio.APIBaseURL(), creds, insightsTimeout)
   148  	iamClient := http.NewUserService(regio.APIBaseURL(), creds, iamTimeout)
   149  
   150  	log.Info().Msg("Running Cypress in Sauce Labs")
   151  	r := saucecloud.CypressRunner{
   152  		Project: p,
   153  		CloudRunner: saucecloud.CloudRunner{
   154  			ProjectUploader: &appsClient,
   155  			JobService: saucecloud.JobService{
   156  				VDCStarter:    &webdriverClient,
   157  				RDCStarter:    &rdcClient,
   158  				VDCReader:     &restoClient,
   159  				RDCReader:     &rdcClient,
   160  				VDCWriter:     &testcompClient,
   161  				VDCStopper:    &restoClient,
   162  				RDCStopper:    &rdcClient,
   163  				VDCDownloader: &restoClient,
   164  			},
   165  			MetadataService: &testcompClient,
   166  			TunnelService:   &restoClient,
   167  			InsightsService: &insightsClient,
   168  			UserService:     &iamClient,
   169  			BuildService:    &restoClient,
   170  			Region:          regio,
   171  			ShowConsoleLog:  p.IsShowConsoleLog(),
   172  			Reporters: createReporters(p.GetReporters(), p.GetNotifications(), p.GetSauceCfg().Metadata, &testcompClient, &restoClient,
   173  				"cypress", "sauce", gFlags.async),
   174  			Async:                  gFlags.async,
   175  			FailFast:               gFlags.failFast,
   176  			MetadataSearchStrategy: framework.NewSearchStrategy(p.GetVersion(), p.GetRootDir()),
   177  			NPMDependencies:        p.GetNpm().Dependencies,
   178  			Retrier: &retry.SauceReportRetrier{
   179  				VDCReader:       &restoClient,
   180  				ProjectUploader: &appsClient,
   181  				Project:         p,
   182  			},
   183  		},
   184  	}
   185  
   186  	p.CleanPackages()
   187  	return r.RunProject()
   188  }