• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 13943722648

19 Mar 2025 09:08AM UTC coverage: 90.112% (-0.4%) from 90.483%
13943722648

push

github

thschmitt
Add run command to execute tests on UiPath Orchestrator

`uipath studio test run` packages the project in the given folder,
uploads the package and executes the tests through the connected
orchestrator instance.

The run command performs the following steps:
- Builds the given project with output type "Tests"
- Uploads the generated nupkg package to Orchestrator
- Gets the existing releases for the package
- The command will either update the release if it is already present,
  or generate a new one if it is not.
- Creates a test set based on the latest release
- Starts the execution of the test set
- Waits for the tests to finish

Implementation:

- Added new TestRunCommand `uipath studio test run` with two parameters:
  `--source .`: Path to UiPath Studio project
  `--timeout 3600`: Time to wait for tests to finish
- Extracted common logic for interacting with the Orchestrator API into
  `orchestratorClient` and using it from the publish and test run
  command
- Extended the progress bar to support showing progress based on the
  number of steps. This is used to display the total number of tests
  and how many have finished:
  `running...        |███                 | (1/6)`
- Extended the `nupkgReader` to parse the package id from the nuspec
  which is used to create the release
- Add `ExecuteAndWait` method to `uipcli`

Issue: https://github.com/UiPath/uipathcli/issues/158

581 of 690 new or added lines in 17 files covered. (84.2%)

2 existing lines in 1 file now uncovered.

5860 of 6503 relevant lines covered (90.11%)

1.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.46
/plugin/studio/test_run_command.go
1
package studio
2

3
import (
4
        "bytes"
5
        "encoding/json"
6
        "fmt"
7
        "net/url"
8
        "os"
9
        "path/filepath"
10
        "strings"
11
        "time"
12

13
        "github.com/UiPath/uipathcli/log"
14
        "github.com/UiPath/uipathcli/output"
15
        "github.com/UiPath/uipathcli/plugin"
16
        "github.com/UiPath/uipathcli/utils/directories"
17
        "github.com/UiPath/uipathcli/utils/process"
18
        "github.com/UiPath/uipathcli/utils/stream"
19
        "github.com/UiPath/uipathcli/utils/visualization"
20
)
21

22
// The TestRunCommand packs a project as a test package,
23
// uploads it to the connected Orchestrator instances
24
// and runs the tests.
25
type TestRunCommand struct {
26
        Exec process.ExecProcess
27
}
28

29
func (c TestRunCommand) Command() plugin.Command {
1✔
30
        return *plugin.NewCommand("studio").
1✔
31
                WithCategory("test", "Test", "Tests your UiPath studio packages").
1✔
32
                WithOperation("run", "Run Tests", "Tests a given package").
1✔
33
                WithParameter("source", plugin.ParameterTypeString, "Path to a project.json file or a folder containing project.json file (default: .)", false).
1✔
34
                WithParameter("timeout", plugin.ParameterTypeInteger, "Time to wait in seconds for tests to finish (default: 3600)", false)
1✔
35
}
1✔
36

37
func (c TestRunCommand) Execute(ctx plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
38
        source, err := c.getSource(ctx)
1✔
39
        if err != nil {
2✔
40
                return err
1✔
41
        }
1✔
42
        timeout := time.Duration(c.getIntParameter("timeout", 3600, ctx.Parameters)) * time.Second
1✔
43
        result, err := c.execute(source, timeout, ctx, logger)
1✔
44
        if err != nil {
2✔
45
                return err
1✔
46
        }
1✔
47

48
        json, err := json.Marshal(result)
1✔
49
        if err != nil {
1✔
NEW
50
                return fmt.Errorf("pack command failed: %v", err)
×
NEW
51
        }
×
52
        return writer.WriteResponse(*output.NewResponseInfo(200, "200 OK", "HTTP/1.1", map[string][]string{}, bytes.NewReader(json)))
1✔
53
}
54

55
func (c TestRunCommand) execute(source string, timeout time.Duration, ctx plugin.ExecutionContext, logger log.Logger) (*testRunResult, error) {
1✔
56
        projectReader := newStudioProjectReader(source)
1✔
57
        project, err := projectReader.ReadMetadata()
1✔
58
        if err != nil {
1✔
NEW
59
                return nil, err
×
NEW
60
        }
×
61
        supported, err := project.TargetFramework.IsSupported()
1✔
62
        if !supported {
1✔
NEW
63
                return nil, err
×
NEW
64
        }
×
65
        tmp, err := directories.Temp()
1✔
66
        if err != nil {
1✔
NEW
67
                return nil, err
×
NEW
68
        }
×
69
        destination := filepath.Join(tmp, ctx.Settings.OperationId)
1✔
70
        defer os.RemoveAll(destination)
1✔
71

1✔
72
        uipcli := newUipcli(c.Exec, logger)
1✔
73
        err = uipcli.Initialize(project.TargetFramework)
1✔
74
        if err != nil {
1✔
NEW
75
                return nil, err
×
NEW
76
        }
×
77

78
        exitCode, stdErr, err := c.executeUipcli(uipcli, source, destination, ctx.Debug, logger)
1✔
79
        if err != nil {
1✔
NEW
80
                return nil, err
×
NEW
81
        }
×
82
        if exitCode != 0 {
1✔
NEW
83
                return nil, fmt.Errorf("Error packaging tests: %v", stdErr)
×
NEW
84
        }
×
85

86
        nupkgPath := findLatestNupkg(destination)
1✔
87
        nupkgReader := newNupkgReader(nupkgPath)
1✔
88
        nuspec, err := nupkgReader.ReadNuspec()
1✔
89
        if err != nil {
2✔
90
                return nil, err
1✔
91
        }
1✔
92

93
        params := newTestRunParams(nupkgPath, nuspec.Id, nuspec.Version, timeout)
1✔
94
        execution, err := c.runTests(*params, ctx, logger)
1✔
95
        if err != nil {
2✔
96
                return nil, err
1✔
97
        }
1✔
98
        return newTestRunResult(*execution), nil
1✔
99
}
100

101
func (c TestRunCommand) runTests(params testRunParams, ctx plugin.ExecutionContext, logger log.Logger) (*TestExecution, error) {
1✔
102
        progressBar := visualization.NewProgressBar(logger)
1✔
103
        defer progressBar.Remove()
1✔
104
        progressBar.UpdatePercentage("uploading...", 0)
1✔
105

1✔
106
        baseUri := c.formatUri(ctx.BaseUri, ctx.Organization, ctx.Tenant)
1✔
107
        client := newOrchestratorClient(baseUri, ctx.Auth, ctx.Debug, ctx.Settings, logger)
1✔
108
        folderId, err := client.GetSharedFolderId()
1✔
109
        if err != nil {
2✔
110
                return nil, err
1✔
111
        }
1✔
112
        file := stream.NewFileStream(params.NupkgPath)
1✔
113
        err = client.Upload(file, progressBar)
1✔
114
        if err != nil {
2✔
115
                return nil, err
1✔
116
        }
1✔
117
        releaseId, err := client.CreateOrUpdateRelease(folderId, params.ProcessKey, params.ProcessVersion)
1✔
118
        if err != nil {
2✔
119
                return nil, err
1✔
120
        }
1✔
121
        testSetId, err := client.CreateTestSet(folderId, releaseId, params.ProcessVersion)
1✔
122
        if err != nil {
2✔
123
                return nil, err
1✔
124
        }
1✔
125
        executionId, err := client.ExecuteTestSet(folderId, testSetId)
1✔
126
        if err != nil {
2✔
127
                return nil, err
1✔
128
        }
1✔
129
        return client.WaitForTestExecutionToFinish(folderId, executionId, params.Timeout, func(execution TestExecution) {
2✔
130
                total := len(execution.TestCaseExecutions)
1✔
131
                completed := 0
1✔
132
                for _, testCase := range execution.TestCaseExecutions {
2✔
133
                        if testCase.IsCompleted() {
2✔
134
                                completed++
1✔
135
                        }
1✔
136
                }
137
                progressBar.UpdateSteps("running...  ", completed, total)
1✔
138
        })
139
}
140

141
func (c TestRunCommand) executeUipcli(uipcli *uipcli, source string, destination string, debug bool, logger log.Logger) (int, string, error) {
1✔
142
        if !debug {
2✔
143
                bar := c.newPackagingProgressBar(logger)
1✔
144
                defer close(bar)
1✔
145
        }
1✔
146
        args := []string{"package", "pack", source, "--outputType", "Tests", "--autoVersion", "--output", destination}
1✔
147
        return uipcli.ExecuteAndWait(args...)
1✔
148
}
149

150
func (c TestRunCommand) newPackagingProgressBar(logger log.Logger) chan struct{} {
1✔
151
        progressBar := visualization.NewProgressBar(logger)
1✔
152
        ticker := time.NewTicker(10 * time.Millisecond)
1✔
153
        cancel := make(chan struct{})
1✔
154
        var percent float64 = 0
1✔
155
        go func() {
2✔
156
                for {
2✔
157
                        select {
1✔
158
                        case <-ticker.C:
1✔
159
                                progressBar.UpdatePercentage("packaging...  ", percent)
1✔
160
                                percent = percent + 1
1✔
161
                                if percent > 100 {
2✔
162
                                        percent = 0
1✔
163
                                }
1✔
164
                        case <-cancel:
1✔
165
                                ticker.Stop()
1✔
166
                                progressBar.Remove()
1✔
167
                                return
1✔
168
                        }
169
                }
170
        }()
171
        return cancel
1✔
172
}
173

174
func (c TestRunCommand) getSource(ctx plugin.ExecutionContext) (string, error) {
1✔
175
        source := c.getParameter("source", ".", ctx.Parameters)
1✔
176
        source, _ = filepath.Abs(source)
1✔
177
        fileInfo, err := os.Stat(source)
1✔
178
        if err != nil {
2✔
179
                return "", fmt.Errorf("%s not found", defaultProjectJson)
1✔
180
        }
1✔
181
        if fileInfo.IsDir() {
2✔
182
                source = filepath.Join(source, defaultProjectJson)
1✔
183
        }
1✔
184
        return source, nil
1✔
185
}
186

187
func (c TestRunCommand) getIntParameter(name string, defaultValue int, parameters []plugin.ExecutionParameter) int {
1✔
188
        result := defaultValue
1✔
189
        for _, p := range parameters {
2✔
190
                if p.Name == name {
2✔
191
                        if data, ok := p.Value.(int); ok {
2✔
192
                                result = data
1✔
193
                                break
1✔
194
                        }
195
                }
196
        }
197
        return result
1✔
198
}
199

200
func (c TestRunCommand) getParameter(name string, defaultValue string, parameters []plugin.ExecutionParameter) string {
1✔
201
        result := defaultValue
1✔
202
        for _, p := range parameters {
2✔
203
                if p.Name == name {
2✔
204
                        if data, ok := p.Value.(string); ok {
2✔
205
                                result = data
1✔
206
                                break
1✔
207
                        }
208
                }
209
        }
210
        return result
1✔
211
}
212

213
func (c TestRunCommand) formatUri(baseUri url.URL, org string, tenant string) string {
1✔
214
        path := baseUri.Path
1✔
215
        if baseUri.Path == "" {
2✔
216
                path = "/{organization}/{tenant}/orchestrator_"
1✔
217
        }
1✔
218
        path = strings.ReplaceAll(path, "{organization}", org)
1✔
219
        path = strings.ReplaceAll(path, "{tenant}", tenant)
1✔
220
        path = strings.TrimSuffix(path, "/")
1✔
221
        return fmt.Sprintf("%s://%s%s", baseUri.Scheme, baseUri.Host, path)
1✔
222
}
223

224
func NewTestRunCommand() *TestRunCommand {
1✔
225
        return &TestRunCommand{process.NewExecProcess()}
1✔
226
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc