Skip to content

Commit

Permalink
feat: ARM simulator support for xcuitest (#917)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexplischke authored Jun 10, 2024
1 parent 6c418d4 commit 3459efb
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 72 deletions.
4 changes: 4 additions & 0 deletions api/saucectl.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2314,6 +2314,10 @@
"description": "The set of one or more versions of the device platform on which to run the test suite.",
"type": "array",
"minItems": 1
},
"armRequired": {
"description": "If set to true, the simulator will run on an ARM-based Mac. If set to false, the simulator will run on an Intel-based Mac.",
"type": "boolean"
}
},
"required": [
Expand Down
4 changes: 4 additions & 0 deletions api/v1alpha/framework/xcuitest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@
"description": "The set of one or more versions of the device platform on which to run the test suite.",
"type": "array",
"minItems": 1
},
"armRequired": {
"description": "If set to true, the simulator will run on an ARM-based Mac. If set to false, the simulator will run on an Intel-based Mac.",
"type": "boolean"
}
},
"required": [
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type VirtualDevice struct {
PlatformName string `yaml:"platformName,omitempty" json:"platformName"`
Orientation string `yaml:"orientation,omitempty" json:"orientation,omitempty"`
PlatformVersions []string `yaml:"platformVersions,omitempty" json:"platformVersions,omitempty"`
ARMRequired bool `yaml:"armRequired,omitempty" json:"armRequired,omitempty"`
}

const (
Expand Down
12 changes: 9 additions & 3 deletions internal/flags/simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/csv"
"errors"
"fmt"
"strconv"
"strings"

"github.com/saucelabs/saucectl/internal/config"
Expand All @@ -16,8 +17,8 @@ type Simulator struct {
Changed bool
}

// String returns a string represenation of the simulator.
func (e Simulator) String() string {
// String returns a string representation of the simulator.
func (e *Simulator) String() string {
if !e.Changed {
return ""
}
Expand Down Expand Up @@ -54,13 +55,18 @@ func (e *Simulator) Set(s string) error {
e.Orientation = val
case "platformVersion":
e.PlatformVersions = []string{val}
case "armRequired":
e.ARMRequired, err = strconv.ParseBool(val)
if err != nil {
return err
}
}
}

return nil
}

// Type returns the value type.
func (e Simulator) Type() string {
func (e *Simulator) Type() string {
return "simulator"
}
7 changes: 5 additions & 2 deletions internal/http/webdriver.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,13 @@ type SauceOpts struct {
TunnelIdentifier string `json:"tunnelIdentifier,omitempty"`
TunnelParent string `json:"parentTunnel,omitempty"` // note that 'parentTunnel` is backwards, because that's the way sauce likes it
ScreenResolution string `json:"screen_resolution,omitempty"`
SauceCloudNode string `json:"_sauceCloudNode,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
Visibility string `json:"public,omitempty"`

// VMD specific settings.

ARMRequired bool `json:"armRequired,omitempty"`
}

type env struct {
Expand Down Expand Up @@ -135,7 +138,6 @@ func (c *Webdriver) StartJob(ctx context.Context, opts job.StartOptions) (jobID
TunnelIdentifier: opts.Tunnel.ID,
TunnelParent: opts.Tunnel.Parent,
ScreenResolution: opts.ScreenResolution,
SauceCloudNode: opts.Experiments["_sauceCloudNode"],
TestName: opts.Name,
BuildName: opts.Build,
Tags: opts.Tags,
Expand All @@ -152,6 +154,7 @@ func (c *Webdriver) StartJob(ctx context.Context, opts job.StartOptions) (jobID
MaxDuration: 10800,
TimeZone: opts.TimeZone,
Visibility: opts.Visibility,
ARMRequired: opts.ARMRequired,
},
DeviceName: opts.DeviceName,
DeviceOrientation: opts.DeviceOrientation,
Expand Down
116 changes: 72 additions & 44 deletions internal/job/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,84 @@ type StartOptions struct {
PrevAttempts []report.Attempt `json:"-"`

// Timeout is used for local/per-suite timeout.
Timeout time.Duration `json:"-"`

User string `json:"username"`
AccessKey string `json:"accessKey"`
App string `json:"app,omitempty"`
TestApp string `json:"testApp,omitempty"`
Suite string `json:"suite,omitempty"`
OtherApps []string `json:"otherApps,omitempty"`
Framework string `json:"framework,omitempty"`
ConfigFilePath string `json:"-"`
CLIFlags map[string]interface{} `json:"-"`
Timeout time.Duration `json:"-"`
StartTime time.Time `json:"startTime,omitempty"`

User string `json:"username"`
AccessKey string `json:"accessKey"`

App string `json:"app,omitempty"`
OtherApps []string `json:"otherApps,omitempty"`

// FrameworkVersion contains the targeted version of the framework
// It should not be confused with automation tool (like jest/folio).
// This is currently supported only for frameworks available on Sauce Cloud:
// Currently supported: Cypress.
Suite string `json:"suite,omitempty"`

// FrameworkVersion contains the targeted version of the framework.
// It should not be confused with RunnerVersion.
FrameworkVersion string `json:"frameworkVersion,omitempty"`
Framework string `json:"framework,omitempty"`

PlatformName string `json:"platformName,omitempty"`
PlatformVersion string `json:"platformVersion,omitempty"`

Tunnel TunnelOptions `json:"tunnel,omitempty"`

Experiments map[string]string `json:"experiments,omitempty"`

// Job Metadata.

Name string `json:"name,omitempty"`
Build string `json:"build,omitempty"`
Tags []string `json:"tags,omitempty"`

// Job Access Control.

Visibility string `json:"public,omitempty"`

// Thresholds & Retries.

Attempt int `json:"-"`
CurrentPassCount int `json:"-"`
BrowserName string `json:"browserName,omitempty"`
BrowserVersion string `json:"browserVersion,omitempty"`
PlatformName string `json:"platformName,omitempty"`
PlatformVersion string `json:"platformVersion,omitempty"`
DeviceID string `json:"deviceId,omitempty"`
Attempt int `json:"-"`
CurrentPassCount int `json:"-"`
PassThreshold int `json:"-"`

Retries int `json:"-"`
SmartRetry SmartRetry `json:"-"`

// Cypress & Playwright & TestCafe only.

BrowserName string `json:"browserName,omitempty"`
BrowserVersion string `json:"browserVersion,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
RunnerVersion string `json:"runnerVersion,omitempty"`
ScreenResolution string `json:"screenResolution,omitempty"`

// RDC & VMD only.

TestApp string `json:"testApp,omitempty"`
DeviceName string `json:"deviceName,omitempty"`
DeviceOrientation string `json:"deviceOrientation"`
DevicePrivateOnly bool `json:"devicePrivateOnly,omitempty"`
DeviceType string `json:"deviceType,omitempty"`
DeviceHasCarrier bool `json:"deviceHasCarrier,omitempty"`
RealDevice bool `json:"realDevice,omitempty"`
Name string `json:"name,omitempty"`
Build string `json:"build,omitempty"`
Tags []string `json:"tags,omitempty"`
Tunnel TunnelOptions `json:"tunnel,omitempty"`
ScreenResolution string `json:"screenResolution,omitempty"`
Retries int `json:"-"`
PassThreshold int `json:"-"`
SmartRetry SmartRetry `json:"-"`
RunnerVersion string `json:"runnerVersion,omitempty"`
Experiments map[string]string `json:"experiments,omitempty"`
TestOptions map[string]interface{} `json:"testOptions,omitempty"`
TestsToRun []string `json:"testsToRun,omitempty"`
TestsToSkip []string `json:"testsToSkip,omitempty"`
StartTime time.Time `json:"startTime,omitempty"`
AppSettings AppSettings `json:"appSettings,omitempty"`
RealDeviceKind string `json:"realDeviceKind,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
Visibility string `json:"public,omitempty"`
Env map[string]string `json:"-"`

// RDC only.

AppSettings AppSettings `json:"appSettings,omitempty"`
DeviceID string `json:"deviceId,omitempty"`
DeviceHasCarrier bool `json:"deviceHasCarrier,omitempty"`
DevicePrivateOnly bool `json:"devicePrivateOnly,omitempty"`
DeviceType string `json:"deviceType,omitempty"`
RealDevice bool `json:"realDevice,omitempty"`
TestsToRun []string `json:"testsToRun,omitempty"`
TestsToSkip []string `json:"testsToSkip,omitempty"`
RealDeviceKind string `json:"realDeviceKind,omitempty"`

// VMD specific settings.

ARMRequired bool `json:"armRequired,omitempty"`
Env map[string]string `json:"-"`

// CLI.

ConfigFilePath string `json:"-"`
CLIFlags map[string]interface{} `json:"-"`
}

// AppSettings represents app settings for real device
Expand Down
21 changes: 0 additions & 21 deletions internal/saucecloud/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/saucelabs/saucectl/internal/apps"
"github.com/saucelabs/saucectl/internal/build"
"github.com/saucelabs/saucectl/internal/config"
"github.com/saucelabs/saucectl/internal/espresso"
"github.com/saucelabs/saucectl/internal/framework"
"github.com/saucelabs/saucectl/internal/hashio"
"github.com/saucelabs/saucectl/internal/iam"
Expand Down Expand Up @@ -284,11 +283,6 @@ func (r *CloudRunner) runJob(opts job.StartOptions) (j job.Job, skipped bool, er
return job.Job{}, r.interrupted, fmt.Errorf("failed to retrieve job status for suite %s: %s", opts.DisplayName, err.Error())
}

// Enrich RDC data
if opts.RealDevice {
enrichRDCReport(&j, opts)
}

// Check timeout
if j.TimedOut {
log.Error().
Expand All @@ -312,21 +306,6 @@ func (r *CloudRunner) runJob(opts job.StartOptions) (j job.Job, skipped bool, er
return j, false, nil
}

// enrichRDCReport added the fields from the opts as the API does not provides it.
func enrichRDCReport(j *job.Job, opts job.StartOptions) {
switch opts.Framework {
case "espresso":
j.OS = espresso.Android
}

if opts.DeviceID != "" {
j.DeviceName = opts.DeviceID
} else {
j.DeviceName = opts.DeviceName
j.OSVersion = opts.PlatformVersion
}
}

func (r *CloudRunner) runJobs(jobOpts chan job.StartOptions, results chan<- result) {
for opts := range jobOpts {
start := time.Now()
Expand Down
2 changes: 2 additions & 0 deletions internal/saucecloud/espresso.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type deviceConfig struct {
hasCarrier bool
deviceType string
privateOnly bool
armRequired bool
}

// EspressoRunner represents the Sauce Labs cloud implementation for cypress.
Expand Down Expand Up @@ -156,6 +157,7 @@ func enumerateDevices(devices []config.Device, virtualDevices []config.VirtualDe
platformName: e.PlatformName,
platformVersion: p,
orientation: e.Orientation,
armRequired: e.ARMRequired,
})
}
}
Expand Down
8 changes: 6 additions & 2 deletions internal/saucecloud/xcuitest.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ func (r *XcuitestRunner) runSuites() bool {
go func() {
for _, s := range suites {
for _, d := range enumerateDevices(s.Devices, s.Simulators) {
log.Debug().Str("suite", s.Name).Str("deviceName", d.name).Str("deviceID", d.ID).Str("platformVersion", d.platformVersion).Msg("Starting job")
log.Debug().Str("suite", s.Name).
Str("deviceName", d.name).Str("deviceID", d.ID).
Str("platformVersion", d.platformVersion).
Msg("Starting job")
r.startJob(jobOpts, s.App, s.TestApp, s.OtherApps, s, d)
}
}
Expand Down Expand Up @@ -235,7 +238,8 @@ func (r *XcuitestRunner) startJob(jobOpts chan<- job.StartOptions, appFileID, te
DevicePrivateOnly: d.privateOnly,

// VMD specific settings
Env: s.Env,
Env: s.Env,
ARMRequired: d.armRequired,

// Overwrite device settings
RealDeviceKind: strings.ToLower(xcuitest.IOS),
Expand Down

0 comments on commit 3459efb

Please sign in to comment.