@nyssance/tauri-hasgard 0.6.1 → 0.6.3

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.
Files changed (3) hide show
  1. package/README.md +70 -61
  2. package/dist/index.js +1 -0
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,101 +1,110 @@
1
1
  # @nyssance/tauri-hasgard
2
2
 
3
- Playwright Test fixtures for real Tauri 3 Wry webviews. The package uses Playwright for test scheduling, assertions, retries, reporters, and artifacts; native application control goes through Hasgard's JSON-RPC bridge.
3
+ Playwright Test fixtures for real Tauri 3 Wry applications. Playwright handles scheduling, retries, assertions, and reports; Hasgard controls native webviews through the plugin.
4
4
 
5
- It deliberately does not expose a fake Playwright `Page`. WKWebView and WebKitGTK do not implement Chromium's CDP contract.
5
+ ## 1. Prepare your app
6
6
 
7
- The fixture object model is `HasgardApplication → HasgardWindow → HasgardLocator`. `hasgard` is the worker-scoped application fixture; `window` is the test-scoped configured primary window.
7
+ [Register the Rust plugin and grant `hasgard:default`](https://github.com/nyssance/tauri-hasgard#register-the-plugin). Use Node.js 24 or newer and install:
8
8
 
9
- ## Setup
9
+ ```sh
10
+ bun add --dev @nyssance/tauri-hasgard @playwright/test
11
+ ```
12
+
13
+ Set a readiness marker in your frontend **after initialization finishes**:
14
+
15
+ ```ts
16
+ document.documentElement.dataset.appReady = "true"
17
+ ```
18
+
19
+ Hasgard waits for this marker before tests start. No Playwright browser download is needed.
20
+
21
+ ## 2. Configure the fixture
22
+
23
+ Create `e2e/fixtures.ts`:
10
24
 
11
25
  ```ts
12
- import { createHasgardTest } from "@nyssance/tauri-hasgard"
26
+ import { join } from "node:path"
27
+
28
+ import { createHasgardTest, hasgardEndpointPath } from "@nyssance/tauri-hasgard"
13
29
  import { expect as playwrightExpect, test as playwrightTest } from "@playwright/test"
14
30
 
15
31
  export const { test, expect } = createHasgardTest({
16
32
  test: playwrightTest,
17
33
  expect: playwrightExpect,
18
- socketPath: (workerIndex) => `/tmp/my-app-e2e-${workerIndex}.sock`,
34
+ socketPath: (workerIndex) => hasgardEndpointPath(`my-app-${process.pid}-${workerIndex}`),
19
35
  windowLabel: "main",
20
- readySelector: '[data-app-ready="true"]',
36
+ readySelector: 'html[data-app-ready="true"]',
21
37
  launch: {
22
38
  command: "bun",
23
39
  args: ["run", "tauri", "dev"],
24
- cwd: import.meta.dirname,
40
+ cwd: join(import.meta.dirname, ".."),
25
41
  timeoutMs: 120_000
26
42
  }
27
43
  })
28
44
  ```
29
45
 
30
- The fixture passes the selected socket to the application as `TAURI_HASGARD_SOCKET`, so each Playwright worker receives an isolated connection.
31
-
32
- On macOS, a managed launch owns a detached process group. Teardown
33
- waits for that group to exit, including children left behind by a launcher,
34
- and escalates from SIGTERM to SIGKILL after five seconds. Startup failures also
35
- clean up, and concurrent `stop()` calls share the same cleanup. A live endpoint
36
- is rejected rather than unlinked; readiness requires a successful connection.
37
- If cleanup fails, ownership is retained so `stop()` can be retried; another
38
- launch is blocked until that cleanup succeeds.
39
- Diagnostics retain the last 64 Ki characters per output stream with a truncation
40
- marker. `await process.diagnostics()` exposes those tails and the actual exit
41
- code or signal. Failed tests attach them; a failed screenshot does not replace
42
- the original test error. Teardown removes the owned stale socket after its
43
- process group exits, checking its identity and preserving live or replaced paths.
44
-
45
- An independent supervisor owns the application and watches the worker's IPC
46
- connection. If the worker is killed with SIGKILL, the supervisor still terminates
47
- the application group and exits. Killing the supervisor itself or children that
48
- deliberately create a new session or process group are outside this guarantee.
49
- Attach-only fixtures do not terminate an application they did not launch.
50
-
51
- `window.press(key)` uses native OS input and, by default, waits for a trusted
52
- WebView keyup before the next press may switch windows. For OS shortcuts and
53
- keys that navigate or close the document, explicitly use
54
- `window.press(key, { completion: "native" })`; that mode confirms posting only.
55
- Bare modifiers also use native posting completion. The active input method
56
- remains in effect: use `fill` for exact text, and `Shift+m` for a shifted key.
57
-
58
- To keep keyboard ownership until an application state is observable:
46
+ Use your app's actual window label and launch command. `hasgardEndpointPath` creates a Unix socket path or Windows named-pipe name. The fixture passes it to the app as `TAURI_HASGARD_SOCKET`.
47
+
48
+ Create `playwright.config.ts` in the app root:
59
49
 
60
50
  ```ts
61
- await window.press("Tab", {
62
- waitFor: "document.activeElement.id === 'save'",
63
- timeoutMs: 1_000
51
+ import { defineConfig } from "@playwright/test"
52
+
53
+ export default defineConfig({
54
+ testDir: "./e2e",
55
+ workers: 1,
56
+ timeout: 30_000,
57
+ expect: { timeout: 5_000 }
64
58
  })
65
59
  ```
66
60
 
67
- The postcondition timeout is capped at ten seconds. Failures retain their
68
- `HasgardRpcError.data.phase` and `injected` status; never blindly retry an
69
- already-injected or uncertain press. See [the wire contract](../../docs/protocol.md#native-keyboard-completion).
61
+ Start with one worker: development servers share ports, and native keyboard input shares desktop focus. For parallel non-keyboard tests, build a debug executable once and launch that binary per worker instead of starting several dev servers. Give each instance separate application data if your app persists state.
70
62
 
71
- Requests are limited to 1,048,576 UTF-8 bytes including the trailing newline,
72
- matching the plugin. Oversized requests fail before transmission. An explicit
73
- null result is supported; malformed responses are rejected instead of returning
74
- an invented value.
63
+ ## 3. Write and run a test
75
64
 
76
- ## Test
65
+ Create `e2e/settings.spec.ts`. Adapt the button, window label, and heading to your UI:
77
66
 
78
67
  ```ts
79
- test("saves settings in a secondary window", async ({ hasgard }) => {
80
- const settings = hasgard.window("settings")
81
- await settings.getByRole("textbox", { name: "Display name" }).fill("Nyssance")
82
- await settings.getByRole("button", { name: "Save", exact: true }).click()
83
- await expect(settings.getByRole("status")).toHaveText("Saved")
68
+ import { expect, test } from "./fixtures"
69
+
70
+ test("opens settings", async ({ hasgard, window }) => {
71
+ await window.getByRole("button", { name: "Settings", exact: true }).click()
72
+ const settings = await hasgard.waitForWindow("settings", 5_000)
73
+ await expect(settings.getByRole("heading", { name: "Settings", exact: true })).toBeVisible()
84
74
  })
85
75
  ```
86
76
 
87
- Use `window` for the configured primary window:
77
+ ```sh
78
+ bunx playwright test
79
+ ```
80
+
81
+ `hasgard` is a worker-scoped `HasgardApplication`; `window` selects the configured primary `HasgardWindow`. Tests in the same worker share application state. Reset data and close secondary windows in your test setup or teardown as needed.
82
+
83
+ For a complete runnable example, see the repository's [fixture app](https://github.com/nyssance/tauri-hasgard/tree/main/examples/fixture-app) and [development instructions](https://github.com/nyssance/tauri-hasgard/blob/main/CONTRIBUTING.md).
84
+
85
+ ## Common operations
88
86
 
89
87
  ```ts
90
- test("opens preferences", async ({ window }) => {
91
- await window.getByRole("button", { name: "Settings", exact: true }).click()
88
+ await window.getByRole("textbox", { name: "Display name", exact: true }).fill("Nyssance")
89
+ await window.locator('[data-testid="save"]').click()
90
+ await expect(window.getByRole("status")).toHaveText("Saved")
91
+ await window.press("Tab", {
92
+ waitFor: "document.activeElement.id === 'save'",
93
+ timeoutMs: 1_000
92
94
  })
93
95
  ```
94
96
 
95
- ## Targets
97
+ - Use `fill` for exact text. `press` sends native input through the active input method.
98
+ - For shortcuts that close or navigate a document, use `press(key, { completion: "native" })`; it confirms posting only. Do not blindly retry a key that may already have been injected.
99
+ - CSS locators use the live selector. Role locators take a fresh accessibility snapshot and require exactly one match for an action.
100
+ - To attach to an existing app, omit `launch` and set `socketPath` to its endpoint. Teardown leaves that app running.
101
+
102
+ ## Lifecycle and limits
103
+
104
+ Managed launches are cleaned up at worker teardown and on startup failure. On macOS/Linux, a supervisor also cleans up the app's process group if the worker is killed. Children that create a separate process group, or killing the supervisor itself, are outside this guarantee.
105
+
106
+ Failed tests using the `window` fixture attach process diagnostics and a screenshot when available. Logs retain a bounded tail. Live endpoints are rejected rather than overwritten.
96
107
 
97
- - `window.locator(css)` sends a CSS selector directly.
98
- - `window.getByRole(role, query)` takes a fresh accessibility snapshot, demands exactly one match for actions, then sends its snapshot ref.
99
- - `byPoint(x, y)` and `byRef(ref)` are available for low-level calls.
108
+ There is no `page` fixture or mocked browser mode. Use `HasgardApplication → HasgardWindow → HasgardLocator`; full Playwright `Page` APIs are not supported.
100
109
 
101
- Ambiguous semantic locators throw. Missing required configuration throws. There is no browser-mode fallback and no silent switch to a different window.
110
+ [Protocol and keyboard semantics](https://github.com/nyssance/tauri-hasgard/blob/main/docs/protocol.md) · [Troubleshooting](https://github.com/nyssance/tauri-hasgard/blob/main/docs/troubleshooting.md)
package/dist/index.js CHANGED
@@ -1072,6 +1072,7 @@ class HasgardWindow {
1072
1072
  }
1073
1073
  async screenshotNative(options) {
1074
1074
  const value = expectRecord(await this.rpc.call("screenshot_native", {
1075
+ window: this.label,
1075
1076
  ...options.windowId === undefined ? {} : { window_id: options.windowId },
1076
1077
  output_path: options.outputPath
1077
1078
  }), "screenshot_native result");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyssance/tauri-hasgard",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Playwright Test fixtures for native Tauri Hasgard automation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@playwright/test": "1.64.1",
35
- "@types/node": "^26.6.5",
35
+ "@types/node": "^26.6.6",
36
36
  "typescript": "^7.0.2",
37
37
  "vitest": "^5.0.5"
38
38
  },