Skip to content

Commit 49a6f82

Browse files
committed
Stabilize Tauri WebDriver E2E
1 parent 773f4a0 commit 49a6f82

7 files changed

Lines changed: 140 additions & 16 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ jobs:
2424
libayatana-appindicator3-dev \
2525
librsvg2-dev \
2626
patchelf \
27-
libxdo-dev
27+
libxdo-dev \
28+
libx11-dev
2829
2930
- name: Set up pnpm
3031
uses: pnpm/action-setup@v6
@@ -101,6 +102,7 @@ jobs:
101102
librsvg2-dev \
102103
patchelf \
103104
libxdo-dev \
105+
libx11-dev \
104106
dbus-x11 \
105107
webkit2gtk-driver \
106108
xvfb
@@ -141,6 +143,7 @@ jobs:
141143
LIBGL_ALWAYS_SOFTWARE: "1"
142144
NO_AT_BRIDGE: "1"
143145
OPENPET_SKIP_TAURI_BUILD: "1"
146+
WEBKIT_DISABLE_COMPOSITING_MODE: "1"
144147
WEBKIT_DISABLE_DMABUF_RENDERER: "1"
145148
run: xvfb-run -a dbus-run-session -- pnpm e2e:tauri
146149

e2e-tauri/wdio.conf.mjs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const rootDir = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
77
const tauriTargetDir = path.join(rootDir, 'src-tauri', 'target', 'debug');
88
const appBinaryName = process.platform === 'win32' ? 'openpet.exe' : 'openpet';
99
const appBinaryPath = path.join(tauriTargetDir, appBinaryName);
10-
const pnpmBinary = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
1110
const tauriDriverBinary =
1211
process.env.TAURI_DRIVER ??
1312
path.join(os.homedir(), '.cargo', 'bin', process.platform === 'win32' ? 'tauri-driver.exe' : 'tauri-driver');
@@ -53,10 +52,7 @@ export const config = {
5352
return;
5453
}
5554

56-
const result = spawnSync(pnpmBinary, ['tauri', 'build', '--debug', '--no-bundle'], {
57-
cwd: rootDir,
58-
stdio: 'inherit',
59-
});
55+
const result = runPnpm(['tauri', 'build', '--debug', '--no-bundle']);
6056

6157
if (result.status !== 0) {
6258
throw new Error(`Tauri debug build failed with exit code ${result.status ?? 'unknown'}.`);
@@ -92,6 +88,21 @@ export const config = {
9288
},
9389
};
9490

91+
function runPnpm(args) {
92+
if (process.env.npm_execpath) {
93+
return spawnSync(process.execPath, [process.env.npm_execpath, ...args], {
94+
cwd: rootDir,
95+
stdio: 'inherit',
96+
});
97+
}
98+
99+
return spawnSync('pnpm', args, {
100+
cwd: rootDir,
101+
stdio: 'inherit',
102+
shell: process.platform === 'win32',
103+
});
104+
}
105+
95106
function closeTauriDriver() {
96107
tauriDriverExitExpected = true;
97108

e2e/openpet-browser.spec.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,92 @@ test.describe('OpenPet browser preview', () => {
4949
await hitTarget.click({ button: 'right' });
5050
await expect(page.getByRole('menuitem', { name: 'Pause walking' })).toBeVisible();
5151
});
52+
53+
test('mocked Tauri pet drag starts only after movement threshold', async ({ page }) => {
54+
await page.addInitScript(() => {
55+
const calls: Array<{ cmd: string; args?: unknown }> = [];
56+
let nextCallbackId = 1;
57+
58+
Object.defineProperty(window, '__openPetTauriCalls', {
59+
configurable: true,
60+
value: calls,
61+
});
62+
63+
Object.defineProperty(window, '__TAURI_EVENT_PLUGIN_INTERNALS__', {
64+
configurable: true,
65+
value: {
66+
unregisterListener: () => {},
67+
},
68+
});
69+
70+
Object.defineProperty(window, '__TAURI_INTERNALS__', {
71+
configurable: true,
72+
value: {
73+
callbacks: {},
74+
convertFileSrc: (filePath: string) => filePath,
75+
invoke: async (cmd: string, args?: unknown) => {
76+
calls.push({ cmd, args });
77+
78+
if (cmd === 'plugin:event|listen') return nextCallbackId++;
79+
if (cmd === 'plugin:event|unlisten') return null;
80+
if (cmd === 'plugin:window|available_monitors') return [];
81+
if (cmd === 'plugin:window|current_monitor') return null;
82+
if (cmd === 'plugin:window|primary_monitor') return null;
83+
if (cmd === 'plugin:window|cursor_position') return { x: 0, y: 0 };
84+
if (cmd === 'plugin:window|inner_position') return { x: 0, y: 0 };
85+
if (cmd === 'plugin:window|scale_factor') return 1;
86+
if (cmd === 'plugin:window|set_ignore_cursor_events') return null;
87+
if (cmd === 'plugin:window|set_position') return null;
88+
if (cmd === 'plugin:window|set_size') return null;
89+
if (cmd === 'plugin:window|start_dragging') return null;
90+
91+
throw new Error(`Unhandled mocked Tauri command: ${cmd}`);
92+
},
93+
metadata: {
94+
currentWebview: { label: 'pet' },
95+
currentWindow: { label: 'pet' },
96+
},
97+
transformCallback: () => nextCallbackId++,
98+
unregisterCallback: () => {},
99+
},
100+
});
101+
});
102+
await page.goto('/?window=pet');
103+
104+
const hitTarget = page.getByTestId('pet-hit-target');
105+
await expect(hitTarget).toBeVisible();
106+
107+
await hitTarget.click();
108+
await expect
109+
.poll(() =>
110+
page.evaluate(
111+
() =>
112+
(window as typeof window & { __openPetTauriCalls: Array<{ cmd: string }> })
113+
.__openPetTauriCalls.filter((call) => call.cmd === 'plugin:window|start_dragging')
114+
.length,
115+
),
116+
)
117+
.toBe(0);
118+
119+
const box = await hitTarget.boundingBox();
120+
expect(box).not.toBeNull();
121+
const x = box!.x + box!.width / 2;
122+
const y = box!.y + box!.height / 2;
123+
124+
await page.mouse.move(x, y);
125+
await page.mouse.down();
126+
await page.mouse.move(x + 12, y);
127+
await page.mouse.up();
128+
129+
await expect
130+
.poll(() =>
131+
page.evaluate(
132+
() =>
133+
(window as typeof window & { __openPetTauriCalls: Array<{ cmd: string }> })
134+
.__openPetTauriCalls.filter((call) => call.cmd === 'plugin:window|start_dragging')
135+
.length,
136+
),
137+
)
138+
.toBeGreaterThan(0);
139+
});
52140
});

src-tauri/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ tauri-plugin-process = "2.3.1"
2424
tauri-plugin-updater = "2.10.1"
2525
toml = "0.9"
2626
url = "2.5.8"
27+
28+
[target.'cfg(target_os = "linux")'.dependencies]
29+
x11 = "2.21.0"

src-tauri/src/main.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,19 @@
44
)]
55

66
fn main() {
7+
init_x11_threads();
78
openpet_lib::run();
89
}
10+
11+
#[cfg(target_os = "linux")]
12+
fn init_x11_threads() {
13+
// Tauri's Linux webview stack can touch Xlib from multiple threads under
14+
// WebDriver/xvfb. Xlib requires XInitThreads to run before any other Xlib
15+
// call, so do it at the process entrypoint before GTK/Tauri initialization.
16+
unsafe {
17+
x11::xlib::XInitThreads();
18+
}
19+
}
20+
21+
#[cfg(not(target_os = "linux"))]
22+
fn init_x11_threads() {}

src/PetWindow.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -370,18 +370,11 @@ export function PetWindow() {
370370
startPointer: { x: event.screenX, y: event.screenY },
371371
latestPointer: { x: event.screenX, y: event.screenY },
372372
startWindow: { x: current.x, y: current.y },
373-
nativeDragging: tauriAvailable,
373+
nativeDragging: false,
374374
started: false,
375375
};
376376
event.currentTarget.setPointerCapture(event.pointerId);
377-
if (!tauriAvailable) return;
378-
const state = dragStateRef.current;
379-
void getCurrentWindow()
380-
.startDragging()
381-
.catch(() => {
382-
if (dragStateRef.current === state && state) state.nativeDragging = false;
383-
});
384-
}, [markActivity, tauriAvailable]);
377+
}, [markActivity]);
385378

386379
const handlePointerMove = useCallback(
387380
(event: ReactPointerEvent<HTMLDivElement>) => {
@@ -396,11 +389,22 @@ export function PetWindow() {
396389
state.started = true;
397390
suppressClickRef.current = true;
398391
setDragActive(true);
392+
if (tauriAvailable) {
393+
state.nativeDragging = true;
394+
void getCurrentWindow()
395+
.startDragging()
396+
.catch(() => {
397+
if (dragStateRef.current !== state) return;
398+
state.nativeDragging = false;
399+
moveManualDrag(state);
400+
});
401+
return;
402+
}
399403
}
400404
if (state.nativeDragging) return;
401405
if (state.started) moveManualDrag(state);
402406
},
403-
[moveManualDrag, setDragActive],
407+
[moveManualDrag, setDragActive, tauriAvailable],
404408
);
405409

406410
const handlePointerUp = useCallback(

0 commit comments

Comments
 (0)