testdriverai 7.11.226-test → 7.11.228-canary
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.
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installs Yaak (an open-source Tauri API client) on Linux, sends an HTTP request,
|
|
3
|
+
* checks the response, then switches the app to dark mode from its Settings dialog.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
7
|
+
|
|
8
|
+
const YAAK_DEB =
|
|
9
|
+
"https://github.com/mountain-loop/yaak/releases/download/v2026.9.0/yaak_2026.9.0_amd64.deb";
|
|
10
|
+
|
|
11
|
+
describe("Yaak (Tauri)", () => {
|
|
12
|
+
it("sends a request, then switches the app to dark mode", async (context) => {
|
|
13
|
+
const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
|
|
14
|
+
|
|
15
|
+
// Download and install the .deb. We start the app ourselves below.
|
|
16
|
+
await testdriver.provision.installer({ url: YAAK_DEB, launch: false });
|
|
17
|
+
|
|
18
|
+
// Pull in Tauri's Linux runtime dependencies (WebKitGTK, libsoup) and start Yaak.
|
|
19
|
+
await testdriver.exec(
|
|
20
|
+
"sh",
|
|
21
|
+
"sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -f -y -qq",
|
|
22
|
+
240000,
|
|
23
|
+
);
|
|
24
|
+
await testdriver.exec("sh", "nohup yaak-app-client >/tmp/yaak.log 2>&1 &", 10000);
|
|
25
|
+
|
|
26
|
+
// Start with an empty workspace. The first find waits for the window to appear.
|
|
27
|
+
await testdriver.find('"Start fresh" card on the Yaak welcome screen', { timeout: 60000 }).click();
|
|
28
|
+
|
|
29
|
+
// Create a new HTTP request, enter a URL and send it.
|
|
30
|
+
await testdriver.find('"New Request" button in the middle of the empty workspace').click();
|
|
31
|
+
await testdriver.find('"HTTP" option in the New Request dropdown menu').click();
|
|
32
|
+
await testdriver.find('URL input with the placeholder "https://example.com" next to the GET method selector').click();
|
|
33
|
+
await testdriver.type("https://httpbin.org/json");
|
|
34
|
+
await testdriver.find("Send button (paper airplane icon) at the right end of the URL bar").click();
|
|
35
|
+
|
|
36
|
+
// Wait for the response, then check what the app rendered.
|
|
37
|
+
await testdriver.find("green 200 OK status label at the top of the response panel", { timeout: 30000 });
|
|
38
|
+
const responseShown = await testdriver.assert(
|
|
39
|
+
'the response panel shows a green 200 OK status and a JSON body containing "Sample Slide Show"',
|
|
40
|
+
);
|
|
41
|
+
expect(responseShown).toBeTruthy();
|
|
42
|
+
|
|
43
|
+
// Open Settings from the gear in Yaak's custom title bar.
|
|
44
|
+
await testdriver.find("settings gear icon in the top-right title bar, just left of the window minimize button").click();
|
|
45
|
+
await testdriver.find('"Settings" item at the top of the main menu dropdown').click();
|
|
46
|
+
|
|
47
|
+
// Switch the appearance from Automatic to Dark.
|
|
48
|
+
await testdriver.find('"Theme" tab in the left sidebar of the Settings dialog, just below "General"').click();
|
|
49
|
+
await testdriver.find('"Automatic" dropdown on the right of the Appearance row in the Theme settings').click();
|
|
50
|
+
await testdriver.find('"Dark" option at the bottom of the Appearance dropdown list').click();
|
|
51
|
+
|
|
52
|
+
// The dropdown updates before the window repaints, so wait for a dark-only detail first.
|
|
53
|
+
await testdriver.find('moon icon next to "Yaak (preview)" on a dark navy background in the Preview section', { timeout: 30000 });
|
|
54
|
+
const darkMode = await testdriver.assert(
|
|
55
|
+
'the Appearance dropdown in the Theme settings shows "Dark" and the window, title bar and settings dialog all use a dark background',
|
|
56
|
+
);
|
|
57
|
+
expect(darkMode).toBeTruthy();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installs Yaak (an open-source Tauri API client) on Linux, sends an HTTP request
|
|
3
|
+
* from its UI, and checks the response it renders.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
7
|
+
|
|
8
|
+
const YAAK_DEB =
|
|
9
|
+
"https://github.com/mountain-loop/yaak/releases/download/v2026.9.0/yaak_2026.9.0_amd64.deb";
|
|
10
|
+
|
|
11
|
+
describe("Yaak (Tauri)", () => {
|
|
12
|
+
it("sends a GET request and shows the JSON response", async (context) => {
|
|
13
|
+
const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
|
|
14
|
+
|
|
15
|
+
// Download and install the .deb. We start the app ourselves below.
|
|
16
|
+
await testdriver.provision.installer({ url: YAAK_DEB, launch: false });
|
|
17
|
+
|
|
18
|
+
// Pull in Tauri's Linux runtime dependencies (WebKitGTK, libsoup) and start Yaak.
|
|
19
|
+
await testdriver.exec(
|
|
20
|
+
"sh",
|
|
21
|
+
"sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -f -y -qq",
|
|
22
|
+
240000,
|
|
23
|
+
);
|
|
24
|
+
await testdriver.exec("sh", "nohup yaak-app-client >/tmp/yaak.log 2>&1 &", 10000);
|
|
25
|
+
|
|
26
|
+
// Start with an empty workspace. The first find waits for the window to appear.
|
|
27
|
+
await testdriver.find('"Start fresh" card on the Yaak welcome screen', { timeout: 60000 }).click();
|
|
28
|
+
|
|
29
|
+
// Create a new HTTP request.
|
|
30
|
+
await testdriver.find('"New Request" button in the middle of the empty workspace').click();
|
|
31
|
+
await testdriver.find('"HTTP" option in the New Request dropdown menu').click();
|
|
32
|
+
|
|
33
|
+
// Enter a URL and send it.
|
|
34
|
+
await testdriver.find('URL input with the placeholder "https://example.com" next to the GET method selector').click();
|
|
35
|
+
await testdriver.type("https://httpbin.org/json");
|
|
36
|
+
await testdriver.find("Send button (paper airplane icon) at the right end of the URL bar").click();
|
|
37
|
+
|
|
38
|
+
// Wait for the response to arrive, then check what the app rendered.
|
|
39
|
+
await testdriver.find("green 200 OK status label at the top of the response panel", { timeout: 30000 });
|
|
40
|
+
const responseShown = await testdriver.assert(
|
|
41
|
+
'the response panel shows a green 200 OK status and a JSON body containing "Sample Slide Show"',
|
|
42
|
+
);
|
|
43
|
+
expect(responseShown).toBeTruthy();
|
|
44
|
+
|
|
45
|
+
const savedInSidebar = await testdriver.assert(
|
|
46
|
+
'the sidebar lists the request as "httpbin.org/json" with a 200 status badge',
|
|
47
|
+
);
|
|
48
|
+
expect(savedInSidebar).toBeTruthy();
|
|
49
|
+
});
|
|
50
|
+
});
|
package/docs/provision.mdx
CHANGED
|
@@ -142,7 +142,7 @@ await testdriver.provision.installer(options)
|
|
|
142
142
|
</ParamField>
|
|
143
143
|
|
|
144
144
|
<ParamField path="launch" type="boolean" default={true}>
|
|
145
|
-
Whether to
|
|
145
|
+
Whether to launch the app after installation. For a `.deb` on Linux, the app is started from its `.desktop` entry (or, when `appName` is set and there is no `.desktop` entry, from its `/usr/bin` binary) and then `appName` is focused. On Windows and macOS, `appName` is focused.
|
|
146
146
|
</ParamField>
|
|
147
147
|
</Expandable>
|
|
148
148
|
</ParamField>
|
|
@@ -151,7 +151,9 @@ await testdriver.provision.installer(options)
|
|
|
151
151
|
- Downloads the installer from the URL
|
|
152
152
|
- Auto-detects the install method based on file extension
|
|
153
153
|
- Runs the appropriate install command (e.g., `msiexec` for `.msi`, `dpkg` for `.deb`)
|
|
154
|
-
-
|
|
154
|
+
- For `.deb` packages, installs any missing dependencies with `apt-get install -f` and fails if the package isn't fully installed
|
|
155
|
+
- For `.deb` packages on Linux with `launch: true`, starts the app in the background
|
|
156
|
+
- Focuses `appName` when it's set and `launch` is `true`
|
|
155
157
|
|
|
156
158
|
```javascript
|
|
157
159
|
// Windows MSI installer
|
|
@@ -160,11 +162,10 @@ await testdriver.provision.installer({
|
|
|
160
162
|
appName: 'MyApp',
|
|
161
163
|
});
|
|
162
164
|
|
|
163
|
-
// Linux DEB package
|
|
165
|
+
// Linux DEB package: installs dependencies, starts the app, focuses it
|
|
164
166
|
await testdriver.provision.installer({
|
|
165
167
|
url: 'https://example.com/app.deb',
|
|
166
168
|
appName: 'MyApp',
|
|
167
|
-
launch: true,
|
|
168
169
|
});
|
|
169
170
|
```
|
|
170
171
|
|
|
@@ -28,6 +28,32 @@ describe("Provision Installer", () => {
|
|
|
28
28
|
},
|
|
29
29
|
);
|
|
30
30
|
|
|
31
|
+
it.skipIf(!isLinux)(
|
|
32
|
+
"should install a .deb with missing dependencies and launch its window",
|
|
33
|
+
async (context) => {
|
|
34
|
+
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
35
|
+
|
|
36
|
+
// Yaak is a Tauri app. Its .deb depends on WebKitGTK and libsoup,
|
|
37
|
+
// which aren't preinstalled, so dpkg alone leaves it unconfigured.
|
|
38
|
+
await testdriver.provision.installer({
|
|
39
|
+
url: 'https://github.com/mountain-loop/yaak/releases/download/v2026.9.0/yaak_2026.9.0_amd64.deb',
|
|
40
|
+
appName: 'Yaak',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// The package and its dependencies are fully installed
|
|
44
|
+
const status = await testdriver.exec(
|
|
45
|
+
'sh',
|
|
46
|
+
"dpkg-query -W -f='${Status}' yaak",
|
|
47
|
+
10000,
|
|
48
|
+
);
|
|
49
|
+
expect(status).toBe('install ok installed');
|
|
50
|
+
|
|
51
|
+
// launch: true (the default) started the app, so its window is on screen
|
|
52
|
+
const result = await testdriver.assert('The Yaak app window is open');
|
|
53
|
+
expect(result).toBeTruthy();
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
31
57
|
it.skipIf(!isLinux)(
|
|
32
58
|
"should download a shell script and verify it exists",
|
|
33
59
|
async (context) => {
|
package/lib/provision.js
CHANGED
|
@@ -479,10 +479,14 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
479
479
|
* @param {string} options.url - URL to download the installer from
|
|
480
480
|
* @param {string} [options.filename] - Filename to save as (auto-detected from URL if not provided)
|
|
481
481
|
* @param {string} [options.appName] - Application name to focus after install
|
|
482
|
-
* @param {boolean} [options.launch=true] - Whether to launch the app after installation
|
|
482
|
+
* @param {boolean} [options.launch=true] - Whether to launch the app after installation.
|
|
483
|
+
* For a .deb on Linux this starts the app from its `.desktop` entry (or, when
|
|
484
|
+
* `appName` is set and there is no `.desktop` entry, its first `/usr/bin` binary),
|
|
485
|
+
* then focuses `appName`. On other platforms it only focuses `appName`.
|
|
483
486
|
* @returns {Promise<string>} Path to the downloaded file
|
|
484
487
|
* @example
|
|
485
|
-
* // Install a .deb package on Linux (auto-detected)
|
|
488
|
+
* // Install a .deb package on Linux (auto-detected). Missing dependencies
|
|
489
|
+
* // are installed with apt, then the app is started and focused.
|
|
486
490
|
* await testdriver.provision.installer({
|
|
487
491
|
* url: 'https://example.com/app.deb',
|
|
488
492
|
* appName: 'MyApp'
|
|
@@ -592,7 +596,25 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
592
596
|
}
|
|
593
597
|
} else if (self.os === "linux") {
|
|
594
598
|
if (ext === "deb") {
|
|
595
|
-
|
|
599
|
+
// dpkg -i exits non-zero when dependencies are missing, so resolve
|
|
600
|
+
// them with apt-get install -f instead of stopping there. Fail the
|
|
601
|
+
// step if the package still isn't fully installed afterwards.
|
|
602
|
+
// Runs in a subshell: the sandbox shell is persistent, so a bare
|
|
603
|
+
// `exit` would kill it instead of failing the command.
|
|
604
|
+
installCommand = [
|
|
605
|
+
`(`,
|
|
606
|
+
`PKG=$(dpkg-deb -f "${actualFilePath}" Package)`,
|
|
607
|
+
`if ! sudo DEBIAN_FRONTEND=noninteractive dpkg -i "${actualFilePath}"; then`,
|
|
608
|
+
` sudo apt-get update -qq`,
|
|
609
|
+
` sudo DEBIAN_FRONTEND=noninteractive apt-get install -f -y`,
|
|
610
|
+
`fi`,
|
|
611
|
+
`if ! dpkg -s "$PKG" 2>/dev/null | grep -q '^Status: install ok installed'; then`,
|
|
612
|
+
` echo "[provision.installer] package $PKG is not fully installed" >&2`,
|
|
613
|
+
` dpkg -l "$PKG" >&2`,
|
|
614
|
+
` exit 1`,
|
|
615
|
+
`fi`,
|
|
616
|
+
`)`,
|
|
617
|
+
].join("\n");
|
|
596
618
|
} else if (ext === "rpm") {
|
|
597
619
|
installCommand = `sudo rpm -i "${actualFilePath}"`;
|
|
598
620
|
} else if (ext === "appimage") {
|
|
@@ -614,8 +636,47 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
614
636
|
console.log(`[provision.installer] ✅ Installation complete`);
|
|
615
637
|
}
|
|
616
638
|
|
|
617
|
-
//
|
|
639
|
+
// On Linux, installing a .deb doesn't start anything. Start the app
|
|
640
|
+
// from its .desktop entry (or a /usr/bin binary when appName is set)
|
|
641
|
+
// so a window exists to focus and test.
|
|
642
|
+
let launched = false;
|
|
643
|
+
if (launch && self.os === "linux" && ext === "deb") {
|
|
644
|
+
const launchScript = [
|
|
645
|
+
`(`,
|
|
646
|
+
`PKG=$(dpkg-deb -f "${actualFilePath}" Package)`,
|
|
647
|
+
`FILES=$(dpkg -L "$PKG")`,
|
|
648
|
+
`DESKTOP=$(echo "$FILES" | grep -E '^/usr/(local/)?share/applications/.*\\.desktop$' | head -n1)`,
|
|
649
|
+
`CMD=""`,
|
|
650
|
+
`if [ -n "$DESKTOP" ]; then`,
|
|
651
|
+
` CMD=$(grep -m1 '^Exec=' "$DESKTOP" | sed -e 's/^Exec=//' -e 's/ *%[a-zA-Z]//g')`,
|
|
652
|
+
`fi`,
|
|
653
|
+
appName
|
|
654
|
+
? `if [ -z "$CMD" ]; then CMD=$(echo "$FILES" | grep -E '^/usr/(local/)?bin/[^/]+$' | head -n1); fi`
|
|
655
|
+
: ``,
|
|
656
|
+
`if [ -n "$CMD" ]; then`,
|
|
657
|
+
` nohup sh -c "$CMD" >/dev/null 2>&1 &`,
|
|
658
|
+
` echo "TD_LAUNCHED=$CMD"`,
|
|
659
|
+
`fi`,
|
|
660
|
+
`)`,
|
|
661
|
+
].join("\n");
|
|
662
|
+
const output = await self.exec(shell, launchScript, 30000, true);
|
|
663
|
+
const match = /TD_LAUNCHED=(.+)/.exec(output || "");
|
|
664
|
+
if (match) {
|
|
665
|
+
launched = true;
|
|
666
|
+
console.log(`[provision.installer] ✅ Launched ${match[1].trim()}`);
|
|
667
|
+
} else {
|
|
668
|
+
console.log(
|
|
669
|
+
`[provision.installer] No .desktop entry found, skipping launch`,
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Focus the app if appName is provided and launch is true
|
|
618
675
|
if (appName && launch) {
|
|
676
|
+
if (launched) {
|
|
677
|
+
// Give the app time to open its first window
|
|
678
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
679
|
+
}
|
|
619
680
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
620
681
|
await self.focusApplication(appName);
|
|
621
682
|
}
|
package/package.json
CHANGED
package/sdk.d.ts
CHANGED
|
@@ -903,7 +903,7 @@ export interface ProvisionInstallerOptions {
|
|
|
903
903
|
filename?: string;
|
|
904
904
|
/** Application name to focus after install */
|
|
905
905
|
appName?: string;
|
|
906
|
-
/** Whether to launch the app after installation (default: true) */
|
|
906
|
+
/** Whether to launch the app after installation (default: true). For a .deb on Linux this starts the app from its .desktop entry, then focuses appName. */
|
|
907
907
|
launch?: boolean;
|
|
908
908
|
}
|
|
909
909
|
|