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  }