Skip to content

Commit

Permalink
Enable Full Support for Multiple XCTest/XCUITest Cases configured in …
Browse files Browse the repository at this point in the history
…xctestrun File (#563)

This PR extends the support for xctestrun files with format version 2, building on the initial implementation merged in #559.

With this update, we enable full support for XCUITests by utilizing the parsed test configurations from an xctestrun file.
  • Loading branch information
bahrimootaz authored Feb 12, 2025
1 parent 1a76f14 commit 35e364b
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 19 deletions.
42 changes: 33 additions & 9 deletions ios/testmanagerd/xctestrunutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"bytes"
"fmt"
"github.com/danielpaulus/go-ios/ios"
"github.com/danielpaulus/go-ios/ios/installationproxy"
"howett.net/plist"
"io"
"maps"
"os"
"path/filepath"
"strings"
)

// xctestrunutils provides utilities for parsing `.xctestrun` files.
Expand All @@ -21,32 +23,43 @@ import (

// schemeData represents the structure of a scheme-specific test configuration
type schemeData struct {
TestHostBundleIdentifier string
TestBundlePath string
SkipTestIdentifiers []string
OnlyTestIdentifiers []string
IsUITestBundle bool
CommandLineArguments []string
EnvironmentVariables map[string]any
TestingEnvironmentVariables map[string]any
TestHostBundleIdentifier string
TestBundlePath string
SkipTestIdentifiers []string
OnlyTestIdentifiers []string
IsUITestBundle bool
CommandLineArguments []string
EnvironmentVariables map[string]any
TestingEnvironmentVariables map[string]any
UITargetAppEnvironmentVariables map[string]any
UITargetAppPath string
}

func (data schemeData) buildTestConfig(device ios.DeviceEntry, listener *TestListener) (TestConfig, error) {
func (data schemeData) buildTestConfig(device ios.DeviceEntry, listener *TestListener, installedApps []installationproxy.AppInfo) (TestConfig, error) {
testsToRun := data.OnlyTestIdentifiers
testsToSkip := data.SkipTestIdentifiers

testEnv := make(map[string]any)
var bundleId string

if data.IsUITestBundle {
maps.Copy(testEnv, data.EnvironmentVariables)
maps.Copy(testEnv, data.TestingEnvironmentVariables)
maps.Copy(testEnv, data.UITargetAppEnvironmentVariables)
// Only call getBundleID if :
// - allAps is provided
// - UITargetAppPath is populated since it can be empty for UI tests in some edge cases
if len(data.UITargetAppPath) > 0 && installedApps != nil {
bundleId = getBundleId(installedApps, data.UITargetAppPath)
}
}

// Extract only the file name
var testBundlePath = filepath.Base(data.TestBundlePath)

// Build the TestConfig object from parsed data
testConfig := TestConfig{
BundleId: bundleId,
TestRunnerBundleId: data.TestHostBundleIdentifier,
XctestConfigName: testBundlePath,
Args: data.CommandLineArguments,
Expand Down Expand Up @@ -173,3 +186,14 @@ func parseVersion2(content []byte) ([]schemeData, error) {
// If we have exactly one TestConfiguration, return the TestTargets
return testConfigs.TestConfigurations[0].TestTargets, nil
}

func getBundleId(installedApps []installationproxy.AppInfo, uiTargetAppPath string) string {
var appNameWithSuffix = filepath.Base(uiTargetAppPath)
var uiTargetAppName = strings.TrimSuffix(appNameWithSuffix, ".app")
for _, app := range installedApps {
if app.CFBundleName == uiTargetAppName {
return app.CFBundleIdentifier
}
}
return ""
}
22 changes: 18 additions & 4 deletions ios/testmanagerd/xctestrunutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package testmanagerd

import (
"github.com/danielpaulus/go-ios/ios"
"github.com/danielpaulus/go-ios/ios/installationproxy"
"github.com/stretchr/testify/assert"
"testing"
)
Expand Down Expand Up @@ -61,7 +62,7 @@ func TestBuildTestConfigV1(t *testing.T) {
// Act: Convert testConfigSpecification to TestConfig
var testConfigs []TestConfig
for _, r := range testConfigSpecification {
tc, _ := r.buildTestConfig(mockDevice, mockListener)
tc, _ := r.buildTestConfig(mockDevice, mockListener, nil)
testConfigs = append(testConfigs, tc)
}

Expand Down Expand Up @@ -118,7 +119,9 @@ func TestParsingV2(t *testing.T) {
SkipTestIdentifiers: []string{
"SkippedTests", "SkippedTests/testThatAlwaysFailsAndShouldBeSkipped",
},
IsUITestBundle: false,
IsUITestBundle: false,
UITargetAppEnvironmentVariables: nil,
UITargetAppPath: "",
},
{
TestHostBundleIdentifier: "saucelabs.FakeCounterAppUITests.xctrunner",
Expand All @@ -134,6 +137,10 @@ func TestParsingV2(t *testing.T) {
OnlyTestIdentifiers: nil,
SkipTestIdentifiers: nil,
IsUITestBundle: true,
UITargetAppEnvironmentVariables: map[string]any{
"APP_DISTRIBUTOR_ID_OVERRIDE": "com.apple.AppStore",
},
UITargetAppPath: "__TESTROOT__/Debug-iphoneos/FakeCounterApp.app",
},
}

Expand All @@ -148,10 +155,16 @@ func TestBuildTestConfigV2(t *testing.T) {
DeviceID: 8110,
}
mockListener := &TestListener{}

// Build allApps mock data to verify the getBundleID function
allAppsMockData := []installationproxy.AppInfo{
{
CFBundleName: "FakeCounterApp",
CFBundleIdentifier: "saucelabs.FakeCounterApp",
},
}
var testConfigs []TestConfig
for _, r := range testConfigSpecification {
tc, _ := r.buildTestConfig(mockDevice, mockListener)
tc, _ := r.buildTestConfig(mockDevice, mockListener, allAppsMockData)
testConfigs = append(testConfigs, tc)
}

Expand All @@ -168,6 +181,7 @@ func TestBuildTestConfigV2(t *testing.T) {
Listener: mockListener,
},
{
BundleId: "saucelabs.FakeCounterApp",
TestRunnerBundleId: "saucelabs.FakeCounterAppUITests.xctrunner",
XctestConfigName: "FakeCounterAppUITests.xctest",
Args: []string{},
Expand Down
23 changes: 17 additions & 6 deletions ios/testmanagerd/xcuitestrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,10 @@ func StartXCTestWithConfig(ctx context.Context, xctestrunFilePath string, device
fmt.Errorf("error parsing xctestrun file: %w", err),
}
}

installedApps := getUserInstalledApps(err, device)
var xcTestTargets []TestConfig
for i, r := range xctestSpecification {
if r.IsUITestBundle {
log.Info("go-ios currently only supports XCTests to run with xctestrun files")
continue
}
tc, err := r.buildTestConfig(device, listener)
tc, err := r.buildTestConfig(device, listener, installedApps)
if err != nil {
return nil, []error{
fmt.Errorf("building test config at index %d: %w", i, err),
Expand Down Expand Up @@ -623,3 +619,18 @@ func getappInfo(bundleID string, apps []installationproxy.AppInfo) (appInfo, err

return appInfo{}, fmt.Errorf("Did not find test app for '%s' on device. Is it installed?", bundleID)
}

func getUserInstalledApps(err error, device ios.DeviceEntry) []installationproxy.AppInfo {
svc, err := installationproxy.New(device)
if err != nil {
log.WithError(err).Debug("we couldn't create ios device connection")
return nil
}
defer svc.Close()
installedApps, err := svc.BrowseUserApps()
if err != nil {
log.WithError(err).Debug("we couldn't fetch the installed user apps")
return nil
}
return installedApps
}

0 comments on commit 35e364b

Please sign in to comment.