github.com/saucelabs/saucectl@v0.175.1/internal/cmd/run/replay.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/flags" 19 "github.com/saucelabs/saucectl/internal/framework" 20 "github.com/saucelabs/saucectl/internal/msg" 21 "github.com/saucelabs/saucectl/internal/puppeteer/replay" 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 // NewReplayCmd creates the 'run' command for replay. 32 func NewReplayCmd() *cobra.Command { 33 sc := flags.SnakeCharmer{Fmap: map[string]*pflag.Flag{}} 34 35 cmd := &cobra.Command{ 36 Use: "replay", 37 Short: "Replay chrome devtools recordings", 38 Long: "Unlike 'saucectl run', this command allows you to bypass the config file partially or entirely by configuring an adhoc suite (--name) via flags.", 39 Example: `saucectl run replay recording.json -c "" --name "My Suite"`, 40 SilenceUsage: true, 41 TraverseChildren: true, 42 PreRunE: func(cmd *cobra.Command, args []string) error { 43 sc.BindAll() 44 return preRun() 45 }, 46 Run: func(cmd *cobra.Command, args []string) { 47 // Test patterns are passed in via positional args. 48 viper.Set("suite::recordings", args) 49 50 exitCode, err := runReplay(cmd, true) 51 if err != nil { 52 log.Err(err).Msg("failed to execute run command") 53 } 54 os.Exit(exitCode) 55 }, 56 } 57 58 sc.Fset = cmd.Flags() 59 60 sc.String("name", "suite::name", "", "Set the name of the job as it will appear on Sauce Labs.") 61 sc.Int("passThreshold", "suite::passThreshold", 1, "The minimum number of successful attempts for a suite to be considered as 'passed'.") 62 63 // Browser & Platform 64 sc.String("browser", "suite::browserName", "chrome", "Set the browser to use. Only chrome is supported at this time.") 65 sc.String("browserVersion", "suite::browserVersion", "", "Set the browser version to use. If not specified, the latest version will be used.") 66 sc.String("platform", "suite::platform", "", "Run against this platform.") 67 68 return cmd 69 } 70 71 func runReplay(cmd *cobra.Command, isCLIDriven bool) (int, error) { 72 if !isCLIDriven { 73 config.ValidateSchema(gFlags.cfgFilePath) 74 } 75 76 p, err := replay.FromFile(gFlags.cfgFilePath) 77 if err != nil { 78 return 1, err 79 } 80 81 if err := applyPuppeteerReplayFlags(&p); err != nil { 82 return 1, err 83 } 84 replay.SetDefaults(&p) 85 86 if err := replay.Validate(&p); err != nil { 87 return 1, err 88 } 89 90 ss, err := replay.ShardSuites(p.Suites) 91 if err != nil { 92 return 1, err 93 } 94 p.Suites = ss 95 96 regio := region.FromString(p.Sauce.Region) 97 if regio == region.USEast4 { 98 return 1, errors.New(msg.NoFrameworkSupport) 99 } 100 101 if !gFlags.noAutoTagging { 102 p.Sauce.Metadata.Tags = append(p.Sauce.Metadata.Tags, ci.GetTags()...) 103 } 104 105 tracker := segment.DefaultTracker 106 if regio == region.Staging { 107 tracker.Enabled = false 108 } 109 110 go func() { 111 props := usage.Properties{} 112 props.SetFramework("puppeteer-replay").SetFlags(cmd.Flags()).SetSauceConfig(p.Sauce). 113 SetArtifacts(p.Artifacts).SetNumSuites(len(p.Suites)).SetJobs(captor.Default.TestResults). 114 SetSlack(p.Notifications.Slack).SetLaunchOrder(p.Sauce.LaunchOrder) 115 tracker.Collect(cases.Title(language.English).String(cmds.FullName(cmd)), props) 116 _ = tracker.Close() 117 }() 118 119 cleanupArtifacts(p.Artifacts) 120 121 return runPuppeteerReplayInSauce(p, regio) 122 } 123 124 func runPuppeteerReplayInSauce(p replay.Project, regio region.Region) (int, error) { 125 log.Info().Msg("Replaying chrome devtools recordings") 126 127 creds := regio.Credentials() 128 restoClient := http.NewResto(regio.APIBaseURL(), creds.Username, creds.AccessKey, 0) 129 restoClient.ArtifactConfig = p.Artifacts.Download 130 testcompClient := http.NewTestComposer(regio.APIBaseURL(), creds, testComposerTimeout) 131 webdriverClient := http.NewWebdriver(regio.WebDriverBaseURL(), creds, webdriverTimeout) 132 appsClient := *http.NewAppStore(regio.APIBaseURL(), creds.Username, creds.AccessKey, gFlags.appStoreTimeout) 133 rdcClient := http.NewRDCService(regio.APIBaseURL(), creds.Username, creds.AccessKey, rdcTimeout, config.ArtifactDownload{}) 134 insightsClient := http.NewInsightsService(regio.APIBaseURL(), creds, insightsTimeout) 135 iamClient := http.NewUserService(regio.APIBaseURL(), creds, iamTimeout) 136 137 r := saucecloud.ReplayRunner{ 138 Project: p, 139 CloudRunner: saucecloud.CloudRunner{ 140 ProjectUploader: &appsClient, 141 JobService: saucecloud.JobService{ 142 VDCStarter: &webdriverClient, 143 RDCStarter: &rdcClient, 144 VDCReader: &restoClient, 145 RDCReader: &rdcClient, 146 VDCWriter: &testcompClient, 147 VDCStopper: &restoClient, 148 RDCStopper: &rdcClient, 149 VDCDownloader: &restoClient, 150 }, 151 TunnelService: &restoClient, 152 MetadataService: &testcompClient, 153 InsightsService: &insightsClient, 154 UserService: &iamClient, 155 BuildService: &restoClient, 156 Region: regio, 157 ShowConsoleLog: p.ShowConsoleLog, 158 Reporters: createReporters(p.Reporters, p.Notifications, p.Sauce.Metadata, &testcompClient, &restoClient, 159 "puppeteer-replay", "sauce", gFlags.async), 160 Async: gFlags.async, 161 FailFast: gFlags.failFast, 162 MetadataSearchStrategy: framework.ExactStrategy{}, 163 Retrier: &retry.BasicRetrier{}, 164 }, 165 } 166 167 return r.RunProject() 168 } 169 170 func applyPuppeteerReplayFlags(p *replay.Project) error { 171 if gFlags.selectedSuite != "" { 172 if err := replay.FilterSuites(p, gFlags.selectedSuite); err != nil { 173 return err 174 } 175 } 176 177 // Use the adhoc suite instead, if one is provided 178 if p.Suite.Name != "" { 179 p.Suites = []replay.Suite{p.Suite} 180 } 181 182 return nil 183 }