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 }