We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Convert None to null when serializing expression arguments
I've spent a good two full days trying to figure why it was not working. 

We have a custom function attached to the window object that I was trying invoke with a deep dictionary object. While debugging I realized that when the framework serializes expression arguments it converts `None` to `undefined` instead of `null`.

In our case we explicitly check for `null` (inside the browser) and that was the reason it was failing.

I understand that you have both options (`undefined` and `null`) in javascript world while there is only `None` in python world. So I guess you had to choose one or another. My question is if it was the right choice here. 

Also, it is a very subtle issue and it is not documented anywhere. 

Below is the code in question.

https://github.com/microsoft/playwright-python/blob/6854c40ad4bdc8e1fbc8016998fdcf179d7120aa/playwright/_impl/_js_handle.py#L101

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `playwright/_impl/_js_handle.py`
Content:
```
1 # Copyright (c) Microsoft Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import math
16 from datetime import datetime
17 from typing import TYPE_CHECKING, Any, Dict, List, Optional
18 
19 from playwright._impl._api_types import Error
20 from playwright._impl._connection import ChannelOwner, from_channel
21 
22 if TYPE_CHECKING:  # pragma: no cover
23     from playwright._impl._element_handle import ElementHandle
24 
25 
26 Serializable = Any
27 
28 
29 class JSHandle(ChannelOwner):
30     def __init__(
31         self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
32     ) -> None:
33         super().__init__(parent, type, guid, initializer)
34         self._preview = self._initializer["preview"]
35         self._channel.on(
36             "previewUpdated", lambda params: self._on_preview_updated(params["preview"])
37         )
38 
39     def __repr__(self) -> str:
40         return f"<JSHandle preview={self._preview}>"
41 
42     def __str__(self) -> str:
43         return self._preview
44 
45     def _on_preview_updated(self, preview: str) -> None:
46         self._preview = preview
47 
48     async def evaluate(self, expression: str, arg: Serializable = None) -> Any:
49         return parse_result(
50             await self._channel.send(
51                 "evaluateExpression",
52                 dict(
53                     expression=expression,
54                     arg=serialize_argument(arg),
55                 ),
56             )
57         )
58 
59     async def evaluate_handle(
60         self, expression: str, arg: Serializable = None
61     ) -> "JSHandle":
62         return from_channel(
63             await self._channel.send(
64                 "evaluateExpressionHandle",
65                 dict(
66                     expression=expression,
67                     arg=serialize_argument(arg),
68                 ),
69             )
70         )
71 
72     async def get_property(self, propertyName: str) -> "JSHandle":
73         return from_channel(
74             await self._channel.send("getProperty", dict(name=propertyName))
75         )
76 
77     async def get_properties(self) -> Dict[str, "JSHandle"]:
78         return {
79             prop["name"]: from_channel(prop["value"])
80             for prop in await self._channel.send("getPropertyList")
81         }
82 
83     def as_element(self) -> Optional["ElementHandle"]:
84         return None
85 
86     async def dispose(self) -> None:
87         await self._channel.send("dispose")
88 
89     async def json_value(self) -> Any:
90         return parse_result(await self._channel.send("jsonValue"))
91 
92 
93 def serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any:
94     if isinstance(value, JSHandle):
95         h = len(handles)
96         handles.append(value._channel)
97         return dict(h=h)
98     if depth > 100:
99         raise Error("Maximum argument depth exceeded")
100     if value is None:
101         return dict(v="undefined")
102     if isinstance(value, float):
103         if value == float("inf"):
104             return dict(v="Infinity")
105         if value == float("-inf"):
106             return dict(v="-Infinity")
107         if value == float("-0"):
108             return dict(v="-0")
109         if math.isnan(value):
110             return dict(v="NaN")
111     if isinstance(value, datetime):
112         return dict(d=value.isoformat() + "Z")
113     if isinstance(value, bool):
114         return {"b": value}
115     if isinstance(value, (int, float)):
116         return {"n": value}
117     if isinstance(value, str):
118         return {"s": value}
119 
120     if isinstance(value, list):
121         result = list(map(lambda a: serialize_value(a, handles, depth + 1), value))
122         return dict(a=result)
123 
124     if isinstance(value, dict):
125         result = []  # type: ignore
126         for name in value:
127             result.append(
128                 {"k": name, "v": serialize_value(value[name], handles, depth + 1)}
129             )
130         return dict(o=result)
131     return dict(v="undefined")
132 
133 
134 def serialize_argument(arg: Serializable = None) -> Any:
135     handles: List[JSHandle] = []
136     value = serialize_value(arg, handles, 0)
137     return dict(value=value, handles=handles)
138 
139 
140 def parse_value(value: Any) -> Any:
141     if value is None:
142         return None
143     if isinstance(value, dict):
144         if "v" in value:
145             v = value["v"]
146             if v == "Infinity":
147                 return float("inf")
148             if v == "-Infinity":
149                 return float("-inf")
150             if v == "-0":
151                 return float("-0")
152             if v == "NaN":
153                 return float("nan")
154             if v == "undefined":
155                 return None
156             if v == "null":
157                 return None
158             return v
159 
160         if "a" in value:
161             return list(map(lambda e: parse_value(e), value["a"]))
162 
163         if "d" in value:
164             return datetime.fromisoformat(value["d"][:-1])
165 
166         if "o" in value:
167             o = value["o"]
168             return {e["k"]: parse_value(e["v"]) for e in o}
169 
170         if "n" in value:
171             return value["n"]
172 
173         if "s" in value:
174             return value["s"]
175 
176         if "b" in value:
177             return value["b"]
178     return value
179 
180 
181 def parse_result(result: Any) -> Any:
182     return parse_value(result)
183
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/playwright/_impl/_js_handle.py b/playwright/_impl/_js_handle.py
--- a/playwright/_impl/_js_handle.py
+++ b/playwright/_impl/_js_handle.py
@@ -98,7 +98,7 @@
     if depth > 100:
         raise Error("Maximum argument depth exceeded")
     if value is None:
-        return dict(v="undefined")
+        return dict(v="null")
     if isinstance(value, float):
         if value == float("inf"):
             return dict(v="Infinity")

{"golden_diff": "diff --git a/playwright/_impl/_js_handle.py b/playwright/_impl/_js_handle.py\n--- a/playwright/_impl/_js_handle.py\n+++ b/playwright/_impl/_js_handle.py\n@@ -98,7 +98,7 @@\n     if depth > 100:\n         raise Error(\"Maximum argument depth exceeded\")\n     if value is None:\n-        return dict(v=\"undefined\")\n+        return dict(v=\"null\")\n     if isinstance(value, float):\n         if value == float(\"inf\"):\n             return dict(v=\"Infinity\")\n", "issue": "Convert None to null when serializing expression arguments\nI've spent a good two full days trying to figure why it was not working. \r\n\r\nWe have a custom function attached to the window object that I was trying invoke with a deep dictionary object. While debugging I realized that when the framework serializes expression arguments it converts `None` to `undefined` instead of `null`.\r\n\r\nIn our case we explicitly check for `null` (inside the browser) and that was the reason it was failing.\r\n\r\nI understand that you have both options (`undefined` and `null`) in javascript world while there is only `None` in python world. So I guess you had to choose one or another. My question is if it was the right choice here. \r\n\r\nAlso, it is a very subtle issue and it is not documented anywhere. \r\n\r\nBelow is the code in question.\r\n\r\nhttps://github.com/microsoft/playwright-python/blob/6854c40ad4bdc8e1fbc8016998fdcf179d7120aa/playwright/_impl/_js_handle.py#L101\n", "before_files": [{"content": "# Copyright (c) Microsoft Corporation.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport math\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Any, Dict, List, Optional\n\nfrom playwright._impl._api_types import Error\nfrom playwright._impl._connection import ChannelOwner, from_channel\n\nif TYPE_CHECKING:  # pragma: no cover\n    from playwright._impl._element_handle import ElementHandle\n\n\nSerializable = Any\n\n\nclass JSHandle(ChannelOwner):\n    def __init__(\n        self, parent: ChannelOwner, type: str, guid: str, initializer: Dict\n    ) -> None:\n        super().__init__(parent, type, guid, initializer)\n        self._preview = self._initializer[\"preview\"]\n        self._channel.on(\n            \"previewUpdated\", lambda params: self._on_preview_updated(params[\"preview\"])\n        )\n\n    def __repr__(self) -> str:\n        return f\"<JSHandle preview={self._preview}>\"\n\n    def __str__(self) -> str:\n        return self._preview\n\n    def _on_preview_updated(self, preview: str) -> None:\n        self._preview = preview\n\n    async def evaluate(self, expression: str, arg: Serializable = None) -> Any:\n        return parse_result(\n            await self._channel.send(\n                \"evaluateExpression\",\n                dict(\n                    expression=expression,\n                    arg=serialize_argument(arg),\n                ),\n            )\n        )\n\n    async def evaluate_handle(\n        self, expression: str, arg: Serializable = None\n    ) -> \"JSHandle\":\n        return from_channel(\n            await self._channel.send(\n                \"evaluateExpressionHandle\",\n                dict(\n                    expression=expression,\n                    arg=serialize_argument(arg),\n                ),\n            )\n        )\n\n    async def get_property(self, propertyName: str) -> \"JSHandle\":\n        return from_channel(\n            await self._channel.send(\"getProperty\", dict(name=propertyName))\n        )\n\n    async def get_properties(self) -> Dict[str, \"JSHandle\"]:\n        return {\n            prop[\"name\"]: from_channel(prop[\"value\"])\n            for prop in await self._channel.send(\"getPropertyList\")\n        }\n\n    def as_element(self) -> Optional[\"ElementHandle\"]:\n        return None\n\n    async def dispose(self) -> None:\n        await self._channel.send(\"dispose\")\n\n    async def json_value(self) -> Any:\n        return parse_result(await self._channel.send(\"jsonValue\"))\n\n\ndef serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any:\n    if isinstance(value, JSHandle):\n        h = len(handles)\n        handles.append(value._channel)\n        return dict(h=h)\n    if depth > 100:\n        raise Error(\"Maximum argument depth exceeded\")\n    if value is None:\n        return dict(v=\"undefined\")\n    if isinstance(value, float):\n        if value == float(\"inf\"):\n            return dict(v=\"Infinity\")\n        if value == float(\"-inf\"):\n            return dict(v=\"-Infinity\")\n        if value == float(\"-0\"):\n            return dict(v=\"-0\")\n        if math.isnan(value):\n            return dict(v=\"NaN\")\n    if isinstance(value, datetime):\n        return dict(d=value.isoformat() + \"Z\")\n    if isinstance(value, bool):\n        return {\"b\": value}\n    if isinstance(value, (int, float)):\n        return {\"n\": value}\n    if isinstance(value, str):\n        return {\"s\": value}\n\n    if isinstance(value, list):\n        result = list(map(lambda a: serialize_value(a, handles, depth + 1), value))\n        return dict(a=result)\n\n    if isinstance(value, dict):\n        result = []  # type: ignore\n        for name in value:\n            result.append(\n                {\"k\": name, \"v\": serialize_value(value[name], handles, depth + 1)}\n            )\n        return dict(o=result)\n    return dict(v=\"undefined\")\n\n\ndef serialize_argument(arg: Serializable = None) -> Any:\n    handles: List[JSHandle] = []\n    value = serialize_value(arg, handles, 0)\n    return dict(value=value, handles=handles)\n\n\ndef parse_value(value: Any) -> Any:\n    if value is None:\n        return None\n    if isinstance(value, dict):\n        if \"v\" in value:\n            v = value[\"v\"]\n            if v == \"Infinity\":\n                return float(\"inf\")\n            if v == \"-Infinity\":\n                return float(\"-inf\")\n            if v == \"-0\":\n                return float(\"-0\")\n            if v == \"NaN\":\n                return float(\"nan\")\n            if v == \"undefined\":\n                return None\n            if v == \"null\":\n                return None\n            return v\n\n        if \"a\" in value:\n            return list(map(lambda e: parse_value(e), value[\"a\"]))\n\n        if \"d\" in value:\n            return datetime.fromisoformat(value[\"d\"][:-1])\n\n        if \"o\" in value:\n            o = value[\"o\"]\n            return {e[\"k\"]: parse_value(e[\"v\"]) for e in o}\n\n        if \"n\" in value:\n            return value[\"n\"]\n\n        if \"s\" in value:\n            return value[\"s\"]\n\n        if \"b\" in value:\n            return value[\"b\"]\n    return value\n\n\ndef parse_result(result: Any) -> Any:\n    return parse_value(result)\n", "path": "playwright/_impl/_js_handle.py"}], "after_files": [{"content": "# Copyright (c) Microsoft Corporation.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport math\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Any, Dict, List, Optional\n\nfrom playwright._impl._api_types import Error\nfrom playwright._impl._connection import ChannelOwner, from_channel\n\nif TYPE_CHECKING:  # pragma: no cover\n    from playwright._impl._element_handle import ElementHandle\n\n\nSerializable = Any\n\n\nclass JSHandle(ChannelOwner):\n    def __init__(\n        self, parent: ChannelOwner, type: str, guid: str, initializer: Dict\n    ) -> None:\n        super().__init__(parent, type, guid, initializer)\n        self._preview = self._initializer[\"preview\"]\n        self._channel.on(\n            \"previewUpdated\", lambda params: self._on_preview_updated(params[\"preview\"])\n        )\n\n    def __repr__(self) -> str:\n        return f\"<JSHandle preview={self._preview}>\"\n\n    def __str__(self) -> str:\n        return self._preview\n\n    def _on_preview_updated(self, preview: str) -> None:\n        self._preview = preview\n\n    async def evaluate(self, expression: str, arg: Serializable = None) -> Any:\n        return parse_result(\n            await self._channel.send(\n                \"evaluateExpression\",\n                dict(\n                    expression=expression,\n                    arg=serialize_argument(arg),\n                ),\n            )\n        )\n\n    async def evaluate_handle(\n        self, expression: str, arg: Serializable = None\n    ) -> \"JSHandle\":\n        return from_channel(\n            await self._channel.send(\n                \"evaluateExpressionHandle\",\n                dict(\n                    expression=expression,\n                    arg=serialize_argument(arg),\n                ),\n            )\n        )\n\n    async def get_property(self, propertyName: str) -> \"JSHandle\":\n        return from_channel(\n            await self._channel.send(\"getProperty\", dict(name=propertyName))\n        )\n\n    async def get_properties(self) -> Dict[str, \"JSHandle\"]:\n        return {\n            prop[\"name\"]: from_channel(prop[\"value\"])\n            for prop in await self._channel.send(\"getPropertyList\")\n        }\n\n    def as_element(self) -> Optional[\"ElementHandle\"]:\n        return None\n\n    async def dispose(self) -> None:\n        await self._channel.send(\"dispose\")\n\n    async def json_value(self) -> Any:\n        return parse_result(await self._channel.send(\"jsonValue\"))\n\n\ndef serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any:\n    if isinstance(value, JSHandle):\n        h = len(handles)\n        handles.append(value._channel)\n        return dict(h=h)\n    if depth > 100:\n        raise Error(\"Maximum argument depth exceeded\")\n    if value is None:\n        return dict(v=\"null\")\n    if isinstance(value, float):\n        if value == float(\"inf\"):\n            return dict(v=\"Infinity\")\n        if value == float(\"-inf\"):\n            return dict(v=\"-Infinity\")\n        if value == float(\"-0\"):\n            return dict(v=\"-0\")\n        if math.isnan(value):\n            return dict(v=\"NaN\")\n    if isinstance(value, datetime):\n        return dict(d=value.isoformat() + \"Z\")\n    if isinstance(value, bool):\n        return {\"b\": value}\n    if isinstance(value, (int, float)):\n        return {\"n\": value}\n    if isinstance(value, str):\n        return {\"s\": value}\n\n    if isinstance(value, list):\n        result = list(map(lambda a: serialize_value(a, handles, depth + 1), value))\n        return dict(a=result)\n\n    if isinstance(value, dict):\n        result = []  # type: ignore\n        for name in value:\n            result.append(\n                {\"k\": name, \"v\": serialize_value(value[name], handles, depth + 1)}\n            )\n        return dict(o=result)\n    return dict(v=\"undefined\")\n\n\ndef serialize_argument(arg: Serializable = None) -> Any:\n    handles: List[JSHandle] = []\n    value = serialize_value(arg, handles, 0)\n    return dict(value=value, handles=handles)\n\n\ndef parse_value(value: Any) -> Any:\n    if value is None:\n        return None\n    if isinstance(value, dict):\n        if \"v\" in value:\n            v = value[\"v\"]\n            if v == \"Infinity\":\n                return float(\"inf\")\n            if v == \"-Infinity\":\n                return float(\"-inf\")\n            if v == \"-0\":\n                return float(\"-0\")\n            if v == \"NaN\":\n                return float(\"nan\")\n            if v == \"undefined\":\n                return None\n            if v == \"null\":\n                return None\n            return v\n\n        if \"a\" in value:\n            return list(map(lambda e: parse_value(e), value[\"a\"]))\n\n        if \"d\" in value:\n            return datetime.fromisoformat(value[\"d\"][:-1])\n\n        if \"o\" in value:\n            o = value[\"o\"]\n            return {e[\"k\"]: parse_value(e[\"v\"]) for e in o}\n\n        if \"n\" in value:\n            return value[\"n\"]\n\n        if \"s\" in value:\n            return value[\"s\"]\n\n        if \"b\" in value:\n            return value[\"b\"]\n    return value\n\n\ndef parse_result(result: Any) -> Any:\n    return parse_value(result)\n", "path": "playwright/_impl/_js_handle.py"}]}