Skip to content

Commit

Permalink
Improve serialization and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
r0x0r committed Apr 18, 2024
1 parent 5fee527 commit c985bd1
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 30 deletions.
78 changes: 78 additions & 0 deletions tests/test_serialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@

import webview
import json
from .util import run_test
import pytest

test_values = [
('int', 1),
('string', 'test'),
('null_value', None),
('object', {'key1': 'value', 'key2': 420}),
('array', [1, 2, 3]),
('mixed', {'key1': 'value', 'key2': [ 1, 2, {'id': 2}], 'nullValue': None}),
('boolean', True),
]

test_value_string = '\n'.join([ f"var {name} = {json.dumps(value)}" for name, value in test_values])

HTML = f"""
<!DOCTYPE html>
<html>
<head>
</head>
<body style="width: 100%; height: 100vh; display: flex;">
<script>
{test_value_string}
var circular = {{key: "test"}}
circular.circular = circular
var testObject = {{id: 1}}
var nonCircular = [testObject, testObject]
var nodes = document.getElementsByTagName("div")
</script>
<div>1</div>
<div style="font-family: Helvetica, Arial, sans-serif; font-size: 34px; font-style: italic; font-weight: 800; width: 100%; display: flex; justify-content; center; align-items: center;">
<h1>THIS IS ONLY A TEST</h1>
</h1></div>
<div>3</div>
</body>
</html>
"""

def test_basic_serialization():
window = webview.create_window('Basic serialization test', html=HTML)
run_test(webview, window, serialization, debug=True)


def test_circular_serialization():
window = webview.create_window('Circular reference test', html=HTML)
run_test(webview, window, circular_reference)


def test_dom_serialization():
window = webview.create_window('DOM serialization test', html=HTML)
run_test(webview, window, dom_serialization, debug=True)


def serialization(window):
for name, expected_value in test_values:
result = window.evaluate_js(name)
assert result == expected_value

def circular_reference(window):
result = window.evaluate_js('circular')
assert result == {'key': 'test', 'circular': '[Circular Reference]'}

result = window.evaluate_js('nonCircular')
assert result == [{'id': 1}, {'id': 1}]


def dom_serialization(window):
result = window.evaluate_js('nodes')
assert len(result) == 3
assert result[0]['innerText'] == '1'
assert result[1]['innerText'].strip() == 'THIS IS ONLY A TEST'
assert result[2]['innerText'] == '3'
5 changes: 4 additions & 1 deletion tests/test_set_title.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ def test_set_title():


def set_title(window):
window.set_title('New title')
assert window.title == 'Set title test'
window.title = 'New title'

assert window.title == 'New title'
35 changes: 30 additions & 5 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import time
import traceback
from multiprocessing import Queue
from typing import Any, Callable, Dict, Iterable, Optional
from uuid import uuid4

import pytest
Expand All @@ -13,11 +14,34 @@


def run_test(
webview, window, thread_func=None, param=None, start_args={}, no_destroy=False, destroy_delay=0, debug=False
):
webview: Any,
window: Any,
thread_func: Optional[Callable] = None,
param: Iterable = (),
start_args: Dict[str, Any] = {},
no_destroy: bool = False,
destroy_delay: float = 0,
debug: bool = False
) -> None:
""""
A test running function that creates a window and runs a thread function in it. Test logic is to be placed in the
thread function. The function will wait for the thread function to finish and then destroy the window. If the thread
function raises an exception, the test will fail and the exception will be printed.
:param webview: webview module
:param window: window instance created with webview.create_window
:param thread_func: function to run in the window.
:param param: positional arguments to pass to the thread function
:param start_args: keyword arguments to pass to webview.start
:param no_destroy: flag indicating whether to destroy the window after the thread function finishes (default: False).
If set to True, the window will not be destroyed and the test will not finish until the window is closed manually.
:param destroy_delay: delay in seconds before destroying the window (default: 0)
:param debug: flag indicating whether to enable debug mode (default: False)
"""
__tracebackhide__ = True
try:
queue = Queue()
queue: Queue = Queue()

if debug:
start_args = {**start_args, 'debug': True}
Expand Down Expand Up @@ -71,10 +95,11 @@ def _create_window(
):
def thread():
try:
logger.info('Thread started')
take_screenshot()
move_mouse_cocoa()
if thread_func:
thread_func(window)
thread_func(window, *thread_param)

destroy_event.set()
except Exception as e:
Expand All @@ -86,7 +111,7 @@ def thread():
args = (thread_param,) if thread_param else ()
destroy_event = _destroy_window(webview, window, destroy_delay)

t = threading.Thread(target=thread, args=args)
t = threading.Thread(target=thread)
t.start()

webview.start(**start_args)
Expand Down
54 changes: 30 additions & 24 deletions webview/js/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,48 +115,54 @@
)
}
function serialize(obj, depth=0, visited=new WeakSet()) {
function serialize(obj, ancestors=[]) {
try {
if (obj instanceof Node) return pywebview.domJSON.toJSON(obj, { metadata: false, serialProperties: true });
if (obj instanceof Window) return 'Window';
if (visited.has(obj)) {
return '[Circular Reference]';
var boundSerialize = serialize.bind(obj);
if (typeof obj !== "object" || obj === null) {
return obj;
}
if (typeof obj === 'object' && obj !== null) {
visited.add(obj);
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
ancestors.pop();
}
if (isArrayLike(obj)) {
obj = tryConvertToArray(obj);
}
if (ancestors.includes(obj)) {
return "[Circular Reference]";
}
ancestors.push(obj);
if (Array.isArray(obj)) {
const arr = obj.map(value => serialize(value, depth + 1, visited));
visited.delete(obj);
return arr;
}
if (isArrayLike(obj)) {
obj = tryConvertToArray(obj);
}
const newObj = {};
for (const key in obj) {
if (typeof obj === 'function') {
continue;
}
newObj[key] = serialize(obj[key], depth + 1, visited);
if (Array.isArray(obj)) {
const arr = obj.map(value => boundSerialize(value, ancestors));
return arr;
}
const newObj = {};
for (const key in obj) {
if (typeof obj === 'function') {
continue;
}
visited.delete(obj);
return newObj;
newObj[key] = boundSerialize(obj[key], ancestors);
}
return newObj;
return obj;
} catch (e) {
console.error(e)
return e.toString();
}
}
return JSON.stringify(serialize(obj));
},
var _serialize = serialize.bind(null);
return JSON.stringify(_serialize(obj));
},
_getNodeId: function (element) {
if (!element) {
Expand Down

0 comments on commit c985bd1

Please sign in to comment.