Skip to content

Commit d1d1219

Browse files
committed
fix: improve script handling in execute commands for better serialization
- Updated the `execute` function in both Electron and Tauri services to use `JSON.stringify` for string scripts, ensuring proper escaping of special characters. - Enhanced test coverage for the `execute` command to validate handling of various script formats, including strings with quotes, newlines, unicode, and backslashes. - Adjusted assertions in tests to reflect the new serialization logic, ensuring accurate expectations for script execution.
1 parent 41ed23c commit d1d1219

6 files changed

Lines changed: 50 additions & 8 deletions

File tree

packages/electron-service/src/commands/execute.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ export async function execute<ReturnValue, InnerArguments extends unknown[]>(
1414
throw new Error('WDIO browser is not yet initialised');
1515
}
1616

17+
const scriptString = typeof script === 'function' ? script.toString() : JSON.stringify(script);
18+
1719
const returnValue = await browser.execute(
1820
function executeWithinElectron(script: string, ...args) {
1921
return window.wdioElectron.execute(script, args);
2022
},
21-
`${script}`,
23+
scriptString,
2224
...args,
2325
);
2426

packages/electron-service/test/commands/execute.spec.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,40 @@ describe('execute Command', () => {
4444

4545
it('should execute a stringified function', async () => {
4646
await execute(globalThis.browser, '() => 1 + 2 + 3');
47-
expect(globalThis.browser.execute).toHaveBeenCalledWith(expect.any(Function), '() => 1 + 2 + 3');
48-
expect(globalThis.wdioElectron.execute).toHaveBeenCalledWith('() => 1 + 2 + 3', []);
47+
expect(globalThis.browser.execute).toHaveBeenCalledWith(expect.any(Function), JSON.stringify('() => 1 + 2 + 3'));
48+
expect(globalThis.wdioElectron.execute).toHaveBeenCalledWith(JSON.stringify('() => 1 + 2 + 3'), []);
49+
});
50+
51+
it('should handle scripts with quotes', async () => {
52+
const scriptWithQuotes = '() => "He said \\"hello\\""';
53+
await execute(globalThis.browser, scriptWithQuotes);
54+
expect(globalThis.browser.execute).toHaveBeenCalledWith(expect.any(Function), JSON.stringify(scriptWithQuotes));
55+
});
56+
57+
it('should handle scripts with newlines', async () => {
58+
const scriptWithNewlines = '() => "line1\\nline2"';
59+
await execute(globalThis.browser, scriptWithNewlines);
60+
expect(globalThis.browser.execute).toHaveBeenCalledWith(expect.any(Function), JSON.stringify(scriptWithNewlines));
61+
});
62+
63+
it('should handle scripts with unicode', async () => {
64+
const scriptWithUnicode = '() => "Hello 世界"';
65+
await execute(globalThis.browser, scriptWithUnicode);
66+
expect(globalThis.browser.execute).toHaveBeenCalledWith(expect.any(Function), JSON.stringify(scriptWithUnicode));
67+
});
68+
69+
it('should handle scripts with backslashes', async () => {
70+
const scriptWithBackslashes = '() => "C:\\\\path\\\\file"';
71+
await execute(globalThis.browser, scriptWithBackslashes);
72+
expect(globalThis.browser.execute).toHaveBeenCalledWith(
73+
expect.any(Function),
74+
JSON.stringify(scriptWithBackslashes),
75+
);
76+
});
77+
78+
it('should handle mixed special characters', async () => {
79+
const script = '() => "Test \\n \\t \\u001b and \\\\ backslash"';
80+
await execute(globalThis.browser, script);
81+
expect(globalThis.browser.execute).toHaveBeenCalledWith(expect.any(Function), JSON.stringify(script));
4982
});
5083
});

packages/tauri-plugin/src/commands.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ pub(crate) async fn execute<R: Runtime>(
6363
let script = if !request.args.is_empty() {
6464
let args_json = serde_json::to_string(&request.args)
6565
.map_err(|e| crate::Error::SerializationError(format!("Failed to serialize args: {}", e)))?;
66-
format!("(function() {{ const __wdio_args = {}; return ({}); }})()", args_json, request.script)
66+
let script_json = serde_json::to_string(&request.script)
67+
.map_err(|e| crate::Error::SerializationError(format!("Failed to serialize script: {}", e)))?;
68+
format!("(function() {{ const __wdio_args = {}; return ({}); }})()", args_json, script_json)
6769
} else {
6870
request.script.clone()
6971
};

packages/tauri-service/src/commands/execute.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ export async function execute<ReturnValue, InnerArguments extends unknown[]>(
6666
}
6767

6868
// Convert function to string - keep parameters intact, plugin will inject tauri as first arg
69-
const scriptString = typeof script === 'function' ? script.toString() : script;
69+
// For strings, use JSON.stringify to safely escape special characters
70+
const scriptString = typeof script === 'function' ? script.toString() : JSON.stringify(script);
7071

7172
// Execute via plugin's execute command with better error handling
7273
// The plugin will inject the Tauri APIs object as the first argument

packages/tauri-service/src/service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,9 @@ export default class TauriWorkerService {
318318
script: string | ((...args: InnerArguments) => ReturnValue),
319319
...args: InnerArguments
320320
): Promise<ReturnValue> {
321-
const scriptString = typeof script === 'function' ? script.toString() : script;
321+
// For strings, use JSON.stringify to safely escape special characters
322+
// For functions, toString() gives the function source which is already valid JS
323+
const scriptString = typeof script === 'function' ? script.toString() : JSON.stringify(script);
322324

323325
if (isEmbedded) {
324326
// For embedded WebDriver: skip console wrapper as console forwarding
@@ -327,6 +329,8 @@ export default class TauriWorkerService {
327329
}
328330

329331
// For tauri-driver: use sync execute with console wrapper
332+
// Note: scriptString is already properly escaped from above - functions use .toString()
333+
// which produces valid JS, strings use JSON.stringify() which also produces valid JS
330334
const wrappedScript = `
331335
${CONSOLE_WRAPPER_SCRIPT}
332336
return (${scriptString}).apply(null, arguments);

packages/tauri-service/test/commands/execute.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ describe('execute', () => {
189189
expect(secondCall[3]).toBe(2);
190190
});
191191

192-
it('should pass strings as-is', async () => {
192+
it('should pass strings properly stringified', async () => {
193193
const mockExecute = vi.fn();
194194
mockExecute.mockResolvedValueOnce(true);
195195
mockExecute.mockResolvedValueOnce(JSON.stringify({ __wdio_value__: 'hello' }));
@@ -199,7 +199,7 @@ describe('execute', () => {
199199
await execute<string, []>(browser, 'return "hello"');
200200

201201
const secondCall = mockExecute.mock.calls[1];
202-
expect(secondCall[1]).toBe('return "hello"');
202+
expect(secondCall[1]).toBe(JSON.stringify('return "hello"'));
203203
});
204204
});
205205

0 commit comments

Comments
 (0)