Skip to content

Commit 367dd0a

Browse files
authored
fix(tauri): improve error handling and response mapping (#198)
* fix(tauri): improve error handling and response mapping - Enhanced the WebDriver server's error handling by adding logging for runtime creation and binding failures, ensuring better diagnostics. - Introduced a mapping for known JavaScript error messages to their corresponding W3C error codes, specifically handling "stale element reference." - Added unit tests to verify the correct mapping of JavaScript errors and the behavior of the new error response methods. * fix(tauri): enhance error handling in port allocation and logging - Improved error handling in the PortManager class to release the first port if the second allocation fails, ensuring resource management. - Updated logging messages in TauriWorkerService to provide clearer error context during plugin initialization and command overriding. - Adjusted mock implementation in tests to validate the new error handling behavior in port allocation.
1 parent 244b8cf commit 367dd0a

5 files changed

Lines changed: 113 additions & 15 deletions

File tree

packages/tauri-plugin-webdriver/src/server/mod.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,13 @@ impl<R: Runtime + 'static> AppState<R> {
5151
/// Start the `WebDriver` HTTP server on the specified port
5252
pub fn start<R: Runtime + 'static>(app: AppHandle<R>, port: u16) {
5353
std::thread::spawn(move || {
54-
let rt = TokioRuntime::new().expect("Failed to create Tokio runtime");
54+
let rt = match TokioRuntime::new() {
55+
Ok(rt) => rt,
56+
Err(e) => {
57+
tracing::error!("Failed to create Tokio runtime for WebDriver server: {}", e);
58+
return;
59+
}
60+
};
5561

5662
rt.block_on(async {
5763
let state = Arc::new(AppState::new(app));
@@ -64,13 +70,22 @@ pub fn start<R: Runtime + 'static>(app: AppHandle<R>, port: u16) {
6470
#[cfg(not(target_os = "android"))]
6571
let addr = SocketAddr::from(([127, 0, 0, 1], port));
6672

67-
tracing::info!("WebDriver server listening on http://{}", addr);
73+
let listener = match tokio::net::TcpListener::bind(addr).await {
74+
Ok(l) => l,
75+
Err(e) => {
76+
tracing::error!(
77+
"Failed to bind WebDriver server to {} — port may already be in use: {}",
78+
addr, e
79+
);
80+
return;
81+
}
82+
};
6883

69-
let listener = tokio::net::TcpListener::bind(addr)
70-
.await
71-
.expect("Failed to bind to address");
84+
tracing::info!("WebDriver server listening on http://{}", addr);
7285

73-
axum::serve(listener, router).await.expect("Server error");
86+
if let Err(e) = axum::serve(listener, router).await {
87+
tracing::error!("WebDriver server error: {}", e);
88+
}
7489
});
7590
});
7691
}

packages/tauri-plugin-webdriver/src/server/response.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ impl WebDriverErrorResponse {
9191
}
9292

9393
pub fn javascript_error(message: &str, stacktrace: Option<String>) -> Self {
94+
// Map known JS error messages to their correct W3C error codes
95+
if message.contains("stale element reference") {
96+
return Self::stale_element_reference();
97+
}
98+
9499
Self::new(
95100
StatusCode::INTERNAL_SERVER_ERROR,
96101
"javascript error",
@@ -99,6 +104,15 @@ impl WebDriverErrorResponse {
99104
)
100105
}
101106

107+
pub fn stale_element_reference() -> Self {
108+
Self::new(
109+
StatusCode::NOT_FOUND,
110+
"stale element reference",
111+
"Element is no longer attached to the DOM",
112+
None,
113+
)
114+
}
115+
102116
pub fn unknown_error(message: &str) -> Self {
103117
Self::new(
104118
StatusCode::INTERNAL_SERVER_ERROR,
@@ -188,3 +202,40 @@ impl IntoResponse for WebDriverErrorResponse {
188202

189203
/// Result type for `WebDriver` handlers
190204
pub type WebDriverResult = Result<WebDriverResponse, WebDriverErrorResponse>;
205+
206+
#[cfg(test)]
207+
mod tests {
208+
use super::*;
209+
210+
#[test]
211+
fn javascript_error_maps_stale_element_reference() {
212+
let err = WebDriverErrorResponse::javascript_error("stale element reference", None);
213+
assert_eq!(err.status, StatusCode::NOT_FOUND);
214+
assert_eq!(err.error, "stale element reference");
215+
}
216+
217+
#[test]
218+
fn javascript_error_preserves_generic_errors() {
219+
let err = WebDriverErrorResponse::javascript_error("TypeError: x is not a function", None);
220+
assert_eq!(err.status, StatusCode::INTERNAL_SERVER_ERROR);
221+
assert_eq!(err.error, "javascript error");
222+
assert_eq!(err.message, "TypeError: x is not a function");
223+
}
224+
225+
#[test]
226+
fn javascript_error_preserves_stacktrace_for_generic_errors() {
227+
let err = WebDriverErrorResponse::javascript_error(
228+
"ReferenceError: foo is not defined",
229+
Some("at eval:1:1".to_string()),
230+
);
231+
assert_eq!(err.error, "javascript error");
232+
assert_eq!(err.stacktrace, Some("at eval:1:1".to_string()));
233+
}
234+
235+
#[test]
236+
fn stale_element_reference_returns_not_found() {
237+
let err = WebDriverErrorResponse::stale_element_reference();
238+
assert_eq!(err.status, StatusCode::NOT_FOUND);
239+
assert_eq!(err.error, "stale element reference");
240+
}
241+
}

packages/tauri-service/src/portManager.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@ export class PortManager {
3737
});
3838
this.usedPorts.add(port);
3939

40-
const nativePort = await getPort({
41-
port: preferredNativePort ?? this.baseNativePort,
42-
host: '127.0.0.1',
43-
exclude: Array.from(this.usedPorts),
44-
});
40+
let nativePort: number;
41+
try {
42+
nativePort = await getPort({
43+
port: preferredNativePort ?? this.baseNativePort,
44+
host: '127.0.0.1',
45+
exclude: Array.from(this.usedPorts),
46+
});
47+
} catch (error) {
48+
this.usedPorts.delete(port);
49+
throw error;
50+
}
4551
this.usedPorts.add(nativePort);
4652

4753
log.debug(`Allocated port pair: main=${port}, native=${nativePort}`);

packages/tauri-service/src/service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export default class TauriWorkerService {
108108
});
109109
log.debug('Tauri plugin initialization complete');
110110
} catch (error) {
111-
log.error('Failed to wait for plugin initialization:', error);
111+
log.error('Failed to wait for plugin initialization — tauri.execute() and mocking may not work:', error);
112112
}
113113
}
114114

@@ -292,8 +292,8 @@ export default class TauriWorkerService {
292292
} as Parameters<typeof browser.overwriteCommand>[1];
293293

294294
browser.overwriteCommand(commandName, testOverride, true);
295-
} catch {
296-
// ignore
295+
} catch (error) {
296+
log.warn(`Failed to override element command '${commandName}':`, error);
297297
}
298298
}
299299

@@ -370,6 +370,6 @@ async function updateAllMocks() {
370370
);
371371
log.debug('All mock updates completed successfully');
372372
} catch (error) {
373-
log.debug('Mock update batch failed:', error);
373+
log.warn('Mock update batch failed:', error);
374374
}
375375
}

packages/tauri-service/test/portManager.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ describe('PortManager', () => {
6565
expect(manager.getUsedPorts()).toContain(4444);
6666
expect(manager.getUsedPorts()).toContain(4445);
6767
});
68+
69+
it('should release first port if second allocation fails', async () => {
70+
const getPort = await import('get-port');
71+
const mockGetPort = vi.mocked(getPort.default);
72+
let callCount = 0;
73+
mockGetPort.mockImplementation(async ({ port }: { port: number }) => {
74+
callCount++;
75+
if (callCount === 2) {
76+
throw new Error('no ports available');
77+
}
78+
return port;
79+
});
80+
81+
await expect(manager.allocatePortPair(5000, 5001)).rejects.toThrow('no ports available');
82+
expect(manager.getUsedPorts()).not.toContain(5000);
83+
expect(manager.getUsedPorts()).toHaveLength(0);
84+
85+
// Restore default mock implementation
86+
mockGetPort.mockImplementation(async ({ port, exclude = [] }: { port: number; exclude: number[] }) => {
87+
let candidate = port;
88+
while (exclude.includes(candidate) || usedPorts.has(candidate)) {
89+
candidate++;
90+
}
91+
return candidate;
92+
});
93+
});
6894
});
6995

7096
describe('allocatePorts', () => {

0 commit comments

Comments
 (0)