Skip to content

Commit

Permalink
feat: Support global node (#944)
Browse files Browse the repository at this point in the history
* feat: Support global node

* refine node runtime selection

* cleanup

* add comment to function

* let chef get nodeVersion to start job

* fix unit test

* revise comment

* set nodeVersion to empty if the runner is not for global node

* refine validation msg

* move runtime to a separate package

* revise

* revert unnecessary settings

* fmt

* fmt

* add comment for RuntimeResponse

* extract runtime setup

* avoid big if block

* add ut for runtime validate

* fix rebased error

* when runtime reaches its EOL, just warn user

* refine runtime validation

* exitCode is really unnecessary

* rename setRunime to setNodeRuntime

* add JSON schema

* rename namingMap

* rename Runtime struct fields

* rename SupportGlobalNode to SupportsRuntime

* rename SelectNode to Find

* lower alias before matching runtime

* introduce removal date

* Update internal/runtime/runtime.go

Co-authored-by: Alex Plischke <alex.plischke@saucelabs.com>

* track node version usage

* update unit test

* make SupportsRuntime more generic

* update method comment

* lower alias inside of findRuntimeByAlias

* refine runtime validation msg

* hmm.. do not change parameter value

* set default runtime if it is supported but not set

* update JSON schema

* refine

* refine validation msg

* rename findRuntimeByAlias

* revert runtime ut

---------

Co-authored-by: Alex Plischke <alex.plischke@saucelabs.com>
  • Loading branch information
tianfeng92 and alexplischke authored Sep 9, 2024
1 parent 5246566 commit a6a8bd1
Show file tree
Hide file tree
Showing 29 changed files with 628 additions and 41 deletions.
22 changes: 22 additions & 0 deletions api/saucectl.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,14 @@
"kind": {
"const": "cypress"
},
"nodeVersion": {
"description": "Specifies the Node.js version for Sauce Cloud. Supports SemVer notation and aliases.",
"examples": [
"v20",
"iron",
"lts"
]
},
"showConsoleLog": {
"description": "Shows suites console.log locally. By default console.log is only shown on failures.",
"type": "boolean"
Expand Down Expand Up @@ -1256,6 +1264,14 @@
"kind": {
"const": "playwright"
},
"nodeVersion": {
"description": "Specifies the Node.js version for Sauce Cloud. Supports SemVer notation and aliases.",
"examples": [
"v20",
"iron",
"lts"
]
},
"showConsoleLog": {
"$ref": "#/allOf/1/then/properties/showConsoleLog"
},
Expand Down Expand Up @@ -1599,6 +1615,9 @@
"kind": {
"const": "testcafe"
},
"nodeVersion": {
"$ref": "#/allOf/2/then/properties/nodeVersion"
},
"showConsoleLog": {
"$ref": "#/allOf/1/then/properties/showConsoleLog"
},
Expand Down Expand Up @@ -2349,6 +2368,9 @@
"kind": {
"const": "playwright-cucumberjs"
},
"nodeVersion": {
"$ref": "#/allOf/2/then/properties/nodeVersion"
},
"showConsoleLog": {
"$ref": "#/allOf/1/then/properties/showConsoleLog"
},
Expand Down
3 changes: 3 additions & 0 deletions api/v1/framework/cypress.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"kind": {
"const": "cypress"
},
"nodeVersion": {
"$ref": "../subschema/common.schema.json#/definitions/nodeVersion"
},
"showConsoleLog": {
"$ref": "../subschema/common.schema.json#/definitions/showConsoleLog"
},
Expand Down
8 changes: 8 additions & 0 deletions api/v1/subschema/common.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@
"default": false
}
}
},
"nodeVersion": {
"description": "Specifies the Node.js version for Sauce Cloud. Supports SemVer notation and aliases.",
"examples": [
"v20",
"iron",
"lts"
]
}
}
}
3 changes: 3 additions & 0 deletions api/v1alpha/framework/playwright-cucumberjs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"kind": {
"const": "playwright-cucumberjs"
},
"nodeVersion": {
"$ref": "../subschema/common.schema.json#/definitions/nodeVersion"
},
"showConsoleLog": {
"$ref": "../subschema/common.schema.json#/definitions/showConsoleLog"
},
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha/framework/playwright.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"kind": {
"const": "playwright"
},
"nodeVersion": {
"$ref": "../subschema/common.schema.json#/definitions/nodeVersion"
},
"showConsoleLog": {
"$ref": "../subschema/common.schema.json#/definitions/showConsoleLog"
},
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha/framework/testcafe.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"kind": {
"const": "testcafe"
},
"nodeVersion": {
"$ref": "../subschema/common.schema.json#/definitions/nodeVersion"
},
"showConsoleLog": {
"$ref": "../subschema/common.schema.json#/definitions/showConsoleLog"
},
Expand Down
8 changes: 8 additions & 0 deletions api/v1alpha/subschema/common.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@
"default": false
}
}
},
"nodeVersion": {
"description": "Specifies the Node.js version for Sauce Cloud. Supports SemVer notation and aliases.",
"examples": [
"v20",
"iron",
"lts"
]
}
}
}
2 changes: 1 addition & 1 deletion internal/cmd/run/cucumber.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func runCucumber(cmd *cobra.Command, isCLIDriven bool) (int, error) {
props.SetFramework("playwright-cucumberjs").SetFVersion(p.Playwright.Version).SetFlags(cmd.Flags()).SetSauceConfig(p.Sauce).
SetArtifacts(p.Artifacts).SetNPM(p.Npm).SetNumSuites(len(p.Suites)).
SetSlack(p.Notifications.Slack).SetSharding(cucumber.IsSharded(p.Suites)).SetLaunchOrder(p.Sauce.LaunchOrder).
SetSmartRetry(p.IsSmartRetried()).SetReporters(p.Reporters)
SetSmartRetry(p.IsSmartRetried()).SetReporters(p.Reporters).SetNodeVersion(p.NodeVersion)
tracker.Collect(cases.Title(language.English).String(cmds.FullName(cmd)), props)
_ = tracker.Close()
}()
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/run/cypress.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func runCypress(cmd *cobra.Command, cflags cypressFlags, isCLIDriven bool) (int,
props.SetFramework("cypress").SetFVersion(p.GetVersion()).SetFlags(cmd.Flags()).SetSauceConfig(p.GetSauceCfg()).
SetArtifacts(p.GetArtifactsCfg()).SetNPM(p.GetNpm()).SetNumSuites(len(p.GetSuites())).
SetSlack(p.GetNotifications().Slack).SetSharding(p.IsSharded()).SetLaunchOrder(p.GetSauceCfg().LaunchOrder).
SetSmartRetry(p.IsSmartRetried()).SetReporters(p.GetReporters())
SetSmartRetry(p.IsSmartRetried()).SetReporters(p.GetReporters()).SetNodeVersion(p.GetNodeVersion())

tracker.Collect(cases.Title(language.English).String(cmds.FullName(cmd)), props)
_ = tracker.Close()
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/run/playwright.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func runPlaywright(cmd *cobra.Command, pf playwrightFlags, isCLIDriven bool) (in
props.SetFramework("playwright").SetFVersion(p.Playwright.Version).SetFlags(cmd.Flags()).SetSauceConfig(p.Sauce).
SetArtifacts(p.Artifacts).SetNPM(p.Npm).SetNumSuites(len(p.Suites)).
SetSlack(p.Notifications.Slack).SetSharding(playwright.IsSharded(p.Suites)).SetLaunchOrder(p.Sauce.LaunchOrder).
SetSmartRetry(p.IsSmartRetried()).SetReporters(p.Reporters)
SetSmartRetry(p.IsSmartRetried()).SetReporters(p.Reporters).SetNodeVersion(p.NodeVersion)
tracker.Collect(cases.Title(language.English).String(cmds.FullName(cmd)), props)
_ = tracker.Close()
}()
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/run/testcafe.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func runTestcafe(cmd *cobra.Command, tcFlags testcafeFlags, isCLIDriven bool) (i
props.SetFramework("testcafe").SetFVersion(p.Testcafe.Version).SetFlags(cmd.Flags()).SetSauceConfig(p.Sauce).
SetArtifacts(p.Artifacts).SetNPM(p.Npm).SetNumSuites(len(p.Suites)).
SetSlack(p.Notifications.Slack).SetSharding(testcafe.IsSharded(p.Suites)).SetLaunchOrder(p.Sauce.LaunchOrder).
SetSmartRetry(p.IsSmartRetried()).SetReporters(p.Reporters)
SetSmartRetry(p.IsSmartRetried()).SetReporters(p.Reporters).SetNodeVersion(p.NodeVersion)
tracker.Collect(cases.Title(language.English).String(cmds.FullName(cmd)), props)
_ = tracker.Close()
}()
Expand Down
1 change: 1 addition & 0 deletions internal/cucumber/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Project struct {
Env map[string]string `yaml:"env,omitempty" json:"env"`
EnvFlag map[string]string `yaml:"-" json:"-"`
Notifications config.Notifications `yaml:"notifications,omitempty" json:"-"`
NodeVersion string `yaml:"nodeVersion,omitempty" json:"nodeVersion,omitempty"`
}

// Playwright represents the playwright setting
Expand Down
2 changes: 2 additions & 0 deletions internal/cypress/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type Project interface {
GetSmartRetry(suiteName string) config.SmartRetry
FilterFailedTests(suiteName string, report saucereport.SauceReport) error
IsSmartRetried() bool
GetNodeVersion() string
SetNodeVersion(string)
}

type project struct {
Expand Down
9 changes: 9 additions & 0 deletions internal/cypress/v1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type Project struct {
Env map[string]string `yaml:"env,omitempty" json:"env"`
EnvFlag map[string]string `yaml:"-" json:"-"`
Notifications config.Notifications `yaml:"notifications,omitempty" json:"-"`
NodeVersion string `yaml:"nodeVersion,omitempty" json:"nodeVersion,omitempty"`
}

// Suite represents the cypress test suite configuration.
Expand Down Expand Up @@ -595,3 +596,11 @@ func (p *Project) IsSmartRetried() bool {
}
return false
}

func (p *Project) GetNodeVersion() string {
return p.NodeVersion
}

func (p *Project) SetNodeVersion(version string) {
p.NodeVersion = version
}
15 changes: 15 additions & 0 deletions internal/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"strings"
"time"

"github.com/saucelabs/saucectl/internal/runtime"
)

// Framework represents a test framework (e.g. cypress).
Expand All @@ -16,6 +18,7 @@ type Framework struct {
type MetadataService interface {
Frameworks(ctx context.Context) ([]string, error)
Versions(ctx context.Context, frameworkName string) ([]Metadata, error)
Runtimes(ctx context.Context) ([]runtime.Runtime, error)
}

// Metadata represents test runner metadata.
Expand All @@ -29,6 +32,7 @@ type Metadata struct {
Platforms []Platform
CloudRunnerVersion string
BrowserDefaults map[string]string
Runtimes []string
}

func (m *Metadata) IsDeprecated() bool {
Expand Down Expand Up @@ -65,3 +69,14 @@ func PlatformNames(platforms []Platform) []string {

return pp
}

// SupportsRuntime checks if the current runner supports the specified runtime.
func (m *Metadata) SupportsRuntime(runtimeName string) bool {
for _, r := range m.Runtimes {
if r == runtimeName {
return true
}
}

return false
}
10 changes: 10 additions & 0 deletions internal/framework/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import (

"github.com/Masterminds/semver/v3"
"github.com/saucelabs/saucectl/internal/node"
"github.com/saucelabs/saucectl/internal/runtime"
)

type MockMetadataService struct {
MockFrameworks func(ctx context.Context) ([]string, error)
MockVersions func(ctx context.Context, frameworkName string) ([]Metadata, error)
MockRuntimes func(ctx context.Context) ([]runtime.Runtime, error)
}

func (m *MockMetadataService) Frameworks(ctx context.Context) ([]string, error) {
Expand All @@ -31,6 +33,14 @@ func (m *MockMetadataService) Versions(ctx context.Context, frameworkName string
return nil, nil
}

func (m *MockMetadataService) Runtimes(ctx context.Context) ([]runtime.Runtime, error) {
if m.MockRuntimes != nil {
return m.MockRuntimes(ctx)
}

return nil, nil
}

func TestFrameworkFind_ExactStrategy(t *testing.T) {
var tests = []struct {
testName string
Expand Down
51 changes: 51 additions & 0 deletions internal/http/testcomposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/hashicorp/go-retryablehttp"
"github.com/saucelabs/saucectl/internal/framework"
"github.com/saucelabs/saucectl/internal/iam"
"github.com/saucelabs/saucectl/internal/runtime"
)

// TestComposer service
Expand All @@ -36,6 +37,7 @@ type FrameworkResponse struct {
Browsers []string
} `json:"platforms"`
BrowserDefaults map[string]string `json:"browserDefaults"`
Runtimes []string `json:"runtimes"`
}

// TokenResponse represents the response body for slack token.
Expand All @@ -49,6 +51,22 @@ type runner struct {
GitRelease string `json:"gitRelease"`
}

// RuntimeResponse represents the response body for getting runtimes.
type RuntimeResponse struct {
Name string `json:"name"`
Releases []Release `json:"releases"`
}

type Release struct {
Version string `json:"version"`
Aliases []string `json:"aliases"`
EOLDate time.Time `json:"eolDate"`
RemovalDate time.Time `json:"removalDate"`
Default bool `json:"default"`

Extra map[string]string `json:"extra"`
}

func NewTestComposer(url string, creds iam.Credentials, timeout time.Duration) TestComposer {
return TestComposer{
HTTPClient: NewRetryableClient(timeout),
Expand Down Expand Up @@ -199,6 +217,7 @@ func (c *TestComposer) Versions(ctx context.Context, frameworkName string) ([]fr
Platforms: platforms,
CloudRunnerVersion: f.Runner.CloudRunnerVersion,
BrowserDefaults: f.BrowserDefaults,
Runtimes: f.Runtimes,
})
}
return frameworks, nil
Expand All @@ -218,3 +237,35 @@ func uniqFrameworkNameSet(frameworks []framework.Framework) []string {
}
return fws
}

func (c *TestComposer) Runtimes(ctx context.Context) ([]runtime.Runtime, error) {
url := fmt.Sprintf("%s/v1/testcomposer/runtimes", c.URL)

req, err := NewRetryableRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(c.Credentials.Username, c.Credentials.AccessKey)

var resp []RuntimeResponse
if err = c.doJSONResponse(req, 200, &resp); err != nil {
return nil, err
}

var runtimes []runtime.Runtime
for _, rt := range resp {
for _, r := range rt.Releases {
runtimes = append(runtimes, runtime.Runtime{
Name: rt.Name,
Version: r.Version,
Alias: r.Aliases,
Default: r.Default,
EOLDate: r.EOLDate,
RemovalDate: r.RemovalDate,
Extra: r.Extra,
})
}
}

return runtimes, nil
}
2 changes: 2 additions & 0 deletions internal/http/webdriver.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type SauceOpts struct {
UserAgent string `json:"user_agent,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
Visibility string `json:"public,omitempty"`
NodeVersion string `json:"nodeVersion,omitempty"`

// VMD specific settings.

Expand Down Expand Up @@ -154,6 +155,7 @@ func (c *Webdriver) StartJob(ctx context.Context, opts job.StartOptions) (jobID
MaxDuration: 10800,
TimeZone: opts.TimeZone,
Visibility: opts.Visibility,
NodeVersion: opts.NodeVersion,
ARMRequired: opts.ARMRequired,
},
DeviceName: opts.DeviceName,
Expand Down
2 changes: 2 additions & 0 deletions internal/job/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type StartOptions struct {
PlatformName string `json:"platformName,omitempty"`
PlatformVersion string `json:"platformVersion,omitempty"`

NodeVersion string `json:"nodeVersion,omitempty"`

Tunnel TunnelOptions `json:"tunnel,omitempty"`

Experiments map[string]string `json:"experiments,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions internal/mocks/frameworks.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"context"

"github.com/saucelabs/saucectl/internal/framework"
"github.com/saucelabs/saucectl/internal/runtime"
)

// FakeFrameworkInfoReader is a mock for the interface framework.MetadataService.
type FakeFrameworkInfoReader struct {
FrameworksFn func(ctx context.Context) ([]string, error)
VersionsFn func(ctx context.Context, frameworkName string) ([]framework.Metadata, error)
RuntimeFn func(ctx context.Context) ([]runtime.Runtime, error)
}

// Frameworks is a wrapper around FrameworksFn.
Expand All @@ -21,3 +23,8 @@ func (fir *FakeFrameworkInfoReader) Frameworks(ctx context.Context) ([]string, e
func (fir *FakeFrameworkInfoReader) Versions(ctx context.Context, frameworkName string) ([]framework.Metadata, error) {
return fir.VersionsFn(ctx, frameworkName)
}

// Runtimes is a wrapper around RuntimesFn.
func (fir *FakeFrameworkInfoReader) Runtimes(ctx context.Context) ([]runtime.Runtime, error) {
return fir.RuntimeFn(ctx)
}
Loading

0 comments on commit a6a8bd1

Please sign in to comment.