@testdriverai/agent 7.11.229-canary → 7.11.232-test
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/_data/examples-manifest.json +16 -16
- package/docs/_scripts/generate-examples.js +38 -45
- package/docs/agent.mdx +174 -93
- package/docs/assert.mdx +73 -269
- package/docs/aws-setup.mdx +196 -314
- package/docs/bring-your-own-machine.mdx +139 -206
- package/docs/cache.mdx +144 -151
- package/docs/caching.mdx +99 -89
- package/docs/captcha.mdx +126 -111
- package/docs/ci-cd.mdx +154 -543
- package/docs/click.mdx +74 -229
- package/docs/client.mdx +177 -231
- package/docs/copilot/auto-healing.mdx +86 -370
- package/docs/copilot/running-tests.mdx +121 -930
- package/docs/customizing-devices.mdx +74 -251
- package/docs/dashcam.mdx +72 -405
- package/docs/debugging-with-screenshots.mdx +105 -371
- package/docs/docs.json +79 -22
- package/docs/double-click.mdx +48 -68
- package/docs/errors.mdx +102 -176
- package/docs/events.mdx +170 -327
- package/docs/examples/{hover-text-with-description.mdx → add-to-cart.mdx} +30 -35
- package/docs/examples/assert.mdx +28 -41
- package/docs/examples/click-button.mdx +71 -0
- package/docs/examples/{hover-image.mdx → click-icon.mdx} +32 -28
- package/docs/examples/element-not-found.mdx +27 -26
- package/docs/examples/findall-coffee-icons.mdx +28 -32
- package/docs/examples/installer.mdx +61 -25
- package/docs/examples/launch-vscode-linux.mdx +28 -51
- package/docs/examples/navigate-and-scroll.mdx +82 -0
- package/docs/examples/parse.mdx +24 -28
- package/docs/examples/press-keys.mdx +28 -25
- package/docs/examples/scroll.mdx +25 -27
- package/docs/examples/type.mdx +26 -21
- package/docs/exec.mdx +92 -256
- package/docs/extract.mdx +69 -170
- package/docs/find.mdx +140 -736
- package/docs/focus-application.mdx +69 -246
- package/docs/generating-tests.mdx +103 -137
- package/docs/guide/best-practices-polling.mdx +65 -141
- package/docs/hosted.mdx +80 -144
- package/docs/hover.mdx +62 -235
- package/docs/locating-elements.mdx +74 -369
- package/docs/making-assertions.mdx +116 -74
- package/docs/mcp-setup.mdx +80 -63
- package/docs/mouse-down.mdx +48 -125
- package/docs/mouse-up.mdx +47 -129
- package/docs/options.mdx +75 -48
- package/docs/parse.mdx +95 -184
- package/docs/performing-actions.mdx +104 -94
- package/docs/press-keys.mdx +59 -299
- package/docs/provision.mdx +137 -182
- package/docs/quickstart-claude.mdx +85 -0
- package/docs/quickstart-github.mdx +62 -32
- package/docs/quickstart-manual.mdx +60 -33
- package/docs/quickstart-vscode.mdx +97 -0
- package/docs/quickstart.mdx +26 -9
- package/docs/redraw.mdx +101 -165
- package/docs/reusable-code.mdx +97 -200
- package/docs/right-click.mdx +47 -89
- package/docs/screenshot.mdx +47 -201
- package/docs/screenshots.mdx +87 -137
- package/docs/scroll.mdx +71 -154
- package/docs/secrets.mdx +96 -91
- package/docs/self-hosted.mdx +83 -142
- package/docs/snippets/enterprise.mdx +15 -0
- package/docs/styles.css +68 -0
- package/docs/test-results-json.mdx +167 -204
- package/docs/type.mdx +56 -304
- package/docs/variables.mdx +121 -81
- package/docs/wait.mdx +33 -31
- package/docs/what-is-testdriver.mdx +80 -32
- package/examples/{hover-text-with-description.test.mjs → add-to-cart.test.mjs} +4 -5
- package/examples/assert.test.mjs +3 -20
- package/examples/{hover-text.test.mjs → click-button.test.mjs} +3 -4
- package/examples/{hover-image.test.mjs → click-icon.test.mjs} +5 -6
- package/examples/element-not-found.test.mjs +3 -5
- package/examples/findall-coffee-icons.test.mjs +3 -3
- package/examples/installer.test.mjs +1 -1
- package/examples/launch-vscode-linux.test.mjs +6 -33
- package/examples/{scroll-keyboard.test.mjs → navigate-and-scroll.test.mjs} +4 -5
- package/examples/parse.test.mjs +1 -2
- package/examples/press-keys.test.mjs +4 -4
- package/examples/scroll.test.mjs +3 -6
- package/examples/type.test.mjs +5 -2
- package/lib/provision.js +6 -2
- package/package.json +1 -1
- package/sdk.d.ts +4 -2
- package/sdk.js +1 -1
- package/docs/claude-mcp-plugin.mdx +0 -244
- package/docs/elements.mdx +0 -606
- package/docs/examples/ai.mdx +0 -78
- package/docs/examples/captcha-api.mdx +0 -92
- package/docs/examples/chrome-extension.mdx +0 -156
- package/docs/examples/drag-and-drop.mdx +0 -100
- package/docs/examples/formatted-logging.mdx +0 -74
- package/docs/examples/hover-text.mdx +0 -70
- package/docs/examples/match-image.mdx +0 -97
- package/docs/examples/scroll-keyboard.mdx +0 -80
- package/docs/examples/scroll-until-text.mdx +0 -109
package/docs/exec.mdx
CHANGED
|
@@ -1,318 +1,154 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "exec()"
|
|
3
3
|
sidebarTitle: "exec"
|
|
4
|
-
description: "
|
|
4
|
+
description: "Run a shell or PowerShell command in the sandbox and get its output."
|
|
5
5
|
icon: "terminal"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Run shell commands (Linux) or PowerShell commands (Windows) in the sandbox.
|
|
11
|
-
|
|
12
|
-
## Syntax
|
|
8
|
+
Run a command in the sandbox, for example to create test files, install software, or launch an app. `exec()` returns the command's output.
|
|
13
9
|
|
|
14
10
|
```javascript
|
|
15
|
-
await testdriver.exec(language, code
|
|
11
|
+
const files = await testdriver.exec({ language: 'sh', code: 'ls ~/Downloads' });
|
|
16
12
|
```
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
- On Linux sandboxes, use `'sh'`. The command runs in Bash.
|
|
15
|
+
- On Windows sandboxes, use `'pwsh'`. The command runs in PowerShell.
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
The language to run: `'sh'` (Shell/Linux) or `'pwsh'` (PowerShell/Windows)
|
|
22
|
-
</ParamField>
|
|
17
|
+
## Signature
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
```typescript
|
|
20
|
+
testdriver.exec(options: ExecOptions): Promise<string>
|
|
21
|
+
```
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
The timeout in milliseconds
|
|
30
|
-
</ParamField>
|
|
23
|
+
## Parameters
|
|
31
24
|
|
|
32
|
-
<ParamField path="
|
|
33
|
-
|
|
25
|
+
<ParamField path="options" type="ExecOptions" required>
|
|
26
|
+
<Expandable title="properties">
|
|
27
|
+
<ParamField path="code" type="string" required>
|
|
28
|
+
The command or script to run. It can span several lines.
|
|
29
|
+
</ParamField>
|
|
30
|
+
<ParamField path="language" type="'sh' | 'pwsh'" default="pwsh">
|
|
31
|
+
The shell to run it in. The default is `'pwsh'`, so set `'sh'` on Linux, which is the default sandbox. On a Windows sandbox, `'sh'` switches to `'pwsh'` with a warning.
|
|
32
|
+
</ParamField>
|
|
33
|
+
<ParamField path="timeout" type="number" default="300000">
|
|
34
|
+
How long the command may run, in milliseconds. The default is 5 minutes.
|
|
35
|
+
</ParamField>
|
|
36
|
+
<ParamField path="silent" type="boolean" default="false">
|
|
37
|
+
Keep the command and its output out of the test log.
|
|
38
|
+
</ParamField>
|
|
39
|
+
</Expandable>
|
|
34
40
|
</ParamField>
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`Promise<string>` - The command output
|
|
42
|
+
Commands run in one shell session that stays open for the whole test. A `cd` or an environment variable you set carries over to the next `exec()` call.
|
|
39
43
|
|
|
40
|
-
##
|
|
44
|
+
## Returns
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
`Promise<string>`. The command's standard output, with leading and trailing whitespace trimmed. Output over 5 MB is cut off.
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
## Errors
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
// List files
|
|
48
|
-
const files = await testdriver.exec('sh', 'ls -la', 5000);
|
|
50
|
+
`exec()` throws when:
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Run a script
|
|
54
|
-
await testdriver.exec('sh', './setup.sh', 60000);
|
|
55
|
-
```
|
|
52
|
+
- The command exits with a non-zero code. The message is `Command failed with exit code <code>: <stderr>`.
|
|
53
|
+
- The command runs longer than `timeout`.
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
To allow a failure, catch the error:
|
|
58
56
|
|
|
59
57
|
```javascript
|
|
60
|
-
|
|
61
|
-
await testdriver.exec('sh', '
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Copy files
|
|
67
|
-
await testdriver.exec('sh', 'cp source.txt dest.txt', 5000);
|
|
68
|
-
|
|
69
|
-
// Delete files
|
|
70
|
-
await testdriver.exec('sh', 'rm test.txt', 5000);
|
|
58
|
+
try {
|
|
59
|
+
await testdriver.exec({ language: 'sh', code: 'pkill -f my-app' });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// pkill exits with 1 when no process matched
|
|
62
|
+
}
|
|
71
63
|
```
|
|
72
64
|
|
|
73
|
-
##
|
|
74
|
-
|
|
75
|
-
Execute PowerShell commands in the Windows sandbox.
|
|
65
|
+
## Examples
|
|
76
66
|
|
|
77
|
-
###
|
|
67
|
+
### Create a file the test needs
|
|
78
68
|
|
|
79
69
|
```javascript
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
await testdriver.exec('pwsh', 'choco install firefox -y', 60000);
|
|
85
|
-
|
|
86
|
-
// Download and run installer
|
|
87
|
-
await testdriver.exec('pwsh', `
|
|
88
|
-
Invoke-WebRequest -Uri "https://example.com/setup.exe" -OutFile "C:\\setup.exe"
|
|
89
|
-
Start-Process -FilePath "C:\\setup.exe" -ArgumentList "/S" -Wait
|
|
90
|
-
`, 120000);
|
|
70
|
+
await testdriver.exec({
|
|
71
|
+
language: 'sh',
|
|
72
|
+
code: 'echo "name,email" > ~/Desktop/contacts.csv',
|
|
73
|
+
});
|
|
91
74
|
```
|
|
92
75
|
|
|
93
|
-
###
|
|
76
|
+
### Read a value and check it
|
|
94
77
|
|
|
95
78
|
```javascript
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Set-Content -Path "C:\\test.txt" -Value "Hello World"
|
|
99
|
-
`, 5000);
|
|
100
|
-
|
|
101
|
-
// Read a file
|
|
102
|
-
const content = await testdriver.exec('pwsh',
|
|
103
|
-
'Get-Content -Path "C:\\test.txt"',
|
|
104
|
-
5000
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
// Copy files
|
|
108
|
-
await testdriver.exec('pwsh',
|
|
109
|
-
'Copy-Item -Path "C:\\source.txt" -Destination "C:\\dest.txt"',
|
|
110
|
-
5000
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
// Delete files
|
|
114
|
-
await testdriver.exec('pwsh',
|
|
115
|
-
'Remove-Item -Path "C:\\test.txt"',
|
|
116
|
-
5000
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
// List directory
|
|
120
|
-
const files = await testdriver.exec('pwsh',
|
|
121
|
-
'Get-ChildItem -Path "C:\\Users\\testdriver\\Documents"',
|
|
122
|
-
5000
|
|
123
|
-
);
|
|
79
|
+
const version = await testdriver.exec({ language: 'sh', code: 'node --version' });
|
|
80
|
+
expect(version).toMatch(/^v\d+/);
|
|
124
81
|
```
|
|
125
82
|
|
|
126
|
-
###
|
|
83
|
+
### Install software on Windows
|
|
127
84
|
|
|
128
85
|
```javascript
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
`, 5000);
|
|
136
|
-
|
|
137
|
-
// Kill a process
|
|
138
|
-
await testdriver.exec('pwsh', 'Stop-Process -Name "chrome" -Force', 5000);
|
|
139
|
-
|
|
140
|
-
// Wait for process
|
|
141
|
-
await testdriver.exec('pwsh', 'Start-Process notepad -Wait', 30000);
|
|
86
|
+
await testdriver.exec({
|
|
87
|
+
language: 'pwsh',
|
|
88
|
+
code: 'choco install firefox -y',
|
|
89
|
+
timeout: 600000,
|
|
90
|
+
silent: true,
|
|
91
|
+
});
|
|
142
92
|
```
|
|
143
93
|
|
|
144
|
-
###
|
|
94
|
+
### Launch an app on Windows
|
|
145
95
|
|
|
146
96
|
```javascript
|
|
147
|
-
|
|
148
|
-
await testdriver.
|
|
149
|
-
|
|
150
|
-
// Get environment variable
|
|
151
|
-
const value = await testdriver.exec('pwsh', '$env:MY_VAR', 5000);
|
|
152
|
-
|
|
153
|
-
// Set persistent environment variable
|
|
154
|
-
await testdriver.exec('pwsh', `
|
|
155
|
-
[Environment]::SetEnvironmentVariable("MY_VAR", "value", "User")
|
|
156
|
-
`, 5000);
|
|
97
|
+
await testdriver.exec({ language: 'pwsh', code: 'Start-Process notepad' });
|
|
98
|
+
await testdriver.focusApplication('Notepad');
|
|
157
99
|
```
|
|
158
100
|
|
|
159
|
-
|
|
101
|
+
To open Chrome, VS Code, Electron apps, or installers, use [`provision`](/provision) instead. It launches the app and waits for it.
|
|
160
102
|
|
|
161
|
-
|
|
162
|
-
// Test connectivity
|
|
163
|
-
const pingResult = await testdriver.exec('pwsh',
|
|
164
|
-
'Test-NetConnection google.com',
|
|
165
|
-
10000
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// Download file
|
|
169
|
-
await testdriver.exec('pwsh', `
|
|
170
|
-
Invoke-WebRequest -Uri "https://example.com/file.zip" -OutFile "C:\\Downloads\\file.zip"
|
|
171
|
-
`, 30000);
|
|
172
|
-
|
|
173
|
-
// Check if port is open
|
|
174
|
-
const portOpen = await testdriver.exec('pwsh',
|
|
175
|
-
'Test-NetConnection -ComputerName localhost -Port 3000',
|
|
176
|
-
5000
|
|
177
|
-
);
|
|
178
|
-
```
|
|
103
|
+
## Positional form
|
|
179
104
|
|
|
180
|
-
|
|
105
|
+
Older tests pass the options as arguments:
|
|
181
106
|
|
|
182
|
-
|
|
107
|
+
```typescript
|
|
108
|
+
testdriver.exec(language: 'sh' | 'pwsh', code: string, timeout?: number, silent?: boolean): Promise<string>
|
|
109
|
+
```
|
|
183
110
|
|
|
184
111
|
```javascript
|
|
185
|
-
|
|
186
|
-
|
|
112
|
+
await testdriver.exec('sh', 'ls ~/Downloads', 5000);
|
|
113
|
+
```
|
|
187
114
|
|
|
188
|
-
|
|
189
|
-
await testdriver.exec('pwsh', 'Start-Process notepad', 5000, true);
|
|
115
|
+
It behaves the same. Use the options object in new tests.
|
|
190
116
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
117
|
+
<Note>
|
|
118
|
+
`language: 'js'` is also accepted. It runs the code in Node on your own machine, not in the sandbox, and returns the value you assign to a `result` variable. Write the JavaScript directly in your test instead.
|
|
119
|
+
</Note>
|
|
194
120
|
|
|
195
|
-
##
|
|
196
|
-
|
|
197
|
-
<Check>
|
|
198
|
-
**Use appropriate timeouts**
|
|
199
|
-
|
|
200
|
-
```javascript
|
|
201
|
-
// Quick operations: 5000ms
|
|
202
|
-
await testdriver.exec('sh', 'ls -la', 5000);
|
|
203
|
-
|
|
204
|
-
// Installations: 30000-60000ms
|
|
205
|
-
await testdriver.exec('pwsh', 'npm install -g package', 30000);
|
|
206
|
-
|
|
207
|
-
// Downloads or complex operations: 60000-120000ms
|
|
208
|
-
await testdriver.exec('pwsh', 'Install-Module Something', 120000);
|
|
209
|
-
```
|
|
210
|
-
</Check>
|
|
211
|
-
|
|
212
|
-
<Check>
|
|
213
|
-
**Handle errors gracefully**
|
|
214
|
-
|
|
215
|
-
```javascript
|
|
216
|
-
try {
|
|
217
|
-
await testdriver.exec('pwsh', 'Some-Command', 5000);
|
|
218
|
-
} catch (error) {
|
|
219
|
-
console.error('Command failed:', error.message);
|
|
220
|
-
// Fallback or retry logic
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
</Check>
|
|
224
|
-
|
|
225
|
-
<Check>
|
|
226
|
-
**Use silent mode for background tasks**
|
|
227
|
-
|
|
228
|
-
```javascript
|
|
229
|
-
// Silent install
|
|
230
|
-
await testdriver.exec('pwsh', 'npm install -g tool', 30000, true);
|
|
231
|
-
|
|
232
|
-
// Background service
|
|
233
|
-
await testdriver.exec('pwsh', 'Start-Service MyService', 5000, true);
|
|
234
|
-
```
|
|
235
|
-
</Check>
|
|
236
|
-
|
|
237
|
-
<Warning>
|
|
238
|
-
**Escape strings properly in PowerShell**
|
|
239
|
-
|
|
240
|
-
Use proper escaping for special characters:
|
|
241
|
-
|
|
242
|
-
```javascript
|
|
243
|
-
// Use backticks for newlines
|
|
244
|
-
await testdriver.exec('pwsh', `
|
|
245
|
-
Write-Host "Line 1\`nLine 2"
|
|
246
|
-
`, 5000);
|
|
247
|
-
|
|
248
|
-
// Use single quotes to avoid variable expansion
|
|
249
|
-
await testdriver.exec('pwsh',
|
|
250
|
-
"Write-Host 'Text with $special chars'",
|
|
251
|
-
5000
|
|
252
|
-
);
|
|
253
|
-
```
|
|
254
|
-
</Warning>
|
|
255
|
-
|
|
256
|
-
## Complete Example
|
|
121
|
+
## Complete example
|
|
257
122
|
|
|
258
|
-
```javascript
|
|
259
|
-
import {
|
|
260
|
-
import TestDriver from 'testdriverai';
|
|
123
|
+
```javascript tests/download.test.mjs
|
|
124
|
+
import { describe, expect, it } from 'vitest';
|
|
125
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
261
126
|
|
|
262
|
-
describe('
|
|
263
|
-
|
|
127
|
+
describe('Downloads', () => {
|
|
128
|
+
it('opens a file created in the sandbox', async (context) => {
|
|
129
|
+
const testdriver = TestDriver(context);
|
|
130
|
+
await testdriver.provision.chrome({ url: 'https://testdriver-sandbox.vercel.app/login' });
|
|
264
131
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
});
|
|
132
|
+
await testdriver.exec({
|
|
133
|
+
language: 'sh',
|
|
134
|
+
code: 'echo "<h1>Hello from exec</h1>" > /tmp/hello.html',
|
|
135
|
+
});
|
|
270
136
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
});
|
|
137
|
+
const contents = await testdriver.exec({ language: 'sh', code: 'cat /tmp/hello.html' });
|
|
138
|
+
expect(contents).toContain('Hello from exec');
|
|
274
139
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
console.log('Files:', files);
|
|
279
|
-
|
|
280
|
-
// Create a file
|
|
281
|
-
await testdriver.exec('sh', 'echo "Hello World" > test.txt', 5000);
|
|
282
|
-
|
|
283
|
-
// Read the file
|
|
284
|
-
const content = await testdriver.exec('sh', 'cat test.txt', 5000);
|
|
285
|
-
expect(content).toContain('Hello World');
|
|
286
|
-
});
|
|
140
|
+
await testdriver.pressKeys(['ctrl', 'l']);
|
|
141
|
+
await testdriver.type('file:///tmp/hello.html');
|
|
142
|
+
await testdriver.pressKeys(['enter']);
|
|
287
143
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
await testdriver.exec('pwsh', 'npm install -g http-server', 30000, true);
|
|
291
|
-
|
|
292
|
-
// Create HTML file
|
|
293
|
-
await testdriver.exec('pwsh', `
|
|
294
|
-
Set-Content -Path "C:\\index.html" -Value "<h1>Test Page</h1>"
|
|
295
|
-
`, 5000);
|
|
296
|
-
|
|
297
|
-
// Start server in background
|
|
298
|
-
await testdriver.exec('pwsh', `
|
|
299
|
-
Start-Process pwsh -ArgumentList "-Command", "http-server C:\\ -p 8080"
|
|
300
|
-
`, 5000, true);
|
|
301
|
-
|
|
302
|
-
await new Promise(r => setTimeout(r, 3000));
|
|
303
|
-
|
|
304
|
-
// Launch browser
|
|
305
|
-
await testdriver.exec('pwsh', `
|
|
306
|
-
Start-Process chrome -ArgumentList "http://localhost:8080"
|
|
307
|
-
`, 5000);
|
|
308
|
-
|
|
309
|
-
await testdriver.focusApplication('Google Chrome');
|
|
144
|
+
const result = await testdriver.assert('the page shows "Hello from exec"');
|
|
145
|
+
expect(result).toBe(true);
|
|
310
146
|
});
|
|
311
147
|
});
|
|
312
148
|
```
|
|
313
149
|
|
|
314
|
-
## Related
|
|
150
|
+
## Related
|
|
315
151
|
|
|
316
|
-
- [
|
|
317
|
-
- [
|
|
318
|
-
- [
|
|
152
|
+
- [Launch apps](/provision): open Chrome, VS Code, and installers
|
|
153
|
+
- [focusApplication()](/focus-application): bring a launched app to the front
|
|
154
|
+
- [Customize devices](/customizing-devices): set up the sandbox before your test
|