소스 검색

all tests fixes

dinhlongviolin1 1 개월 전
부모
커밋
02b9bfde67
49개의 변경된 파일1383개의 추가작업 그리고 278개의 파일을 삭제
  1. 0 3
      .github/actions/gui-test/e2e/action.yml
  2. 0 3
      .github/actions/gui-test/prefix/action.yml
  3. 1 0
      pytest.ini
  4. 0 3
      taipy/gui/.gitignore
  5. 10 1
      taipy/gui/gui.py
  6. 50 10
      taipy/gui/servers/fastapi/server.py
  7. 6 0
      taipy/gui/servers/fastapi/utils.py
  8. 11 0
      taipy/gui/servers/flask/server.py
  9. 5 1
      taipy/gui/servers/request.py
  10. 14 0
      taipy/gui/servers/server.py
  11. 3 5
      tests/conftest.py
  12. 3 4
      tests/gui/actions/test_actions.py
  13. 73 10
      tests/gui/actions/test_download.py
  14. 4 4
      tests/gui/actions/test_get_module_context.py
  15. 2 2
      tests/gui/actions/test_get_state_id.py
  16. 3 3
      tests/gui/actions/test_get_user_content_url.py
  17. 33 3
      tests/gui/actions/test_hold_control.py
  18. 5 4
      tests/gui/actions/test_invoke_callback.py
  19. 33 5
      tests/gui/actions/test_navigate.py
  20. 72 12
      tests/gui/actions/test_notify.py
  21. 31 3
      tests/gui/actions/test_resume_control.py
  22. 9 2
      tests/gui/conftest.py
  23. 14 14
      tests/gui/content_provider/test_content_provider.py
  24. 6 6
      tests/gui/data/test_pandas_data_accessor.py
  25. 26 1
      tests/gui/gui_specific/test_favicon.py
  26. 7 8
      tests/gui/gui_specific/test_multiple_instances.py
  27. 42 0
      tests/gui/gui_specific/test_navigate.py
  28. 5 6
      tests/gui/gui_specific/test_partial.py
  29. 2 3
      tests/gui/gui_specific/test_render_route.py
  30. 12 9
      tests/gui/gui_specific/test_variable_binding.py
  31. 67 1
      tests/gui/helpers.py
  32. 4 1
      tests/gui/hooks/test_listener.py
  33. 3 0
      tests/gui/notebook/.gitignore
  34. 187 0
      tests/gui/notebook/simple_gui_fastapi.ipynb
  35. 45 0
      tests/gui/notebook/test_notebook_simple_gui.py
  36. 2 1
      tests/gui/renderers/test_html_parsing.py
  37. 4 4
      tests/gui/server/http/test_extension.py
  38. 113 16
      tests/gui/server/http/test_file_upload.py
  39. 4 4
      tests/gui/server/http/test_image_path.py
  40. 30 19
      tests/gui/server/http/test_status.py
  41. 6 6
      tests/gui/server/http/test_user_content.py
  42. 40 2
      tests/gui/server/ws/test_a.py
  43. 34 2
      tests/gui/server/ws/test_broadcast.py
  44. 36 0
      tests/gui/server/ws/test_df.py
  45. 133 81
      tests/gui/server/ws/test_du.py
  46. 71 4
      tests/gui/server/ws/test_on_change.py
  47. 40 2
      tests/gui/server/ws/test_ru.py
  48. 41 6
      tests/gui/server/ws/test_u.py
  49. 41 4
      tests/gui/server/ws/test_with.py

+ 0 - 3
.github/actions/gui-test/e2e/action.yml

@@ -11,6 +11,3 @@ runs:
     - name: run tests
       shell: bash
       run: pipenv run pytest -m "teste2e" tests/gui
-    - name: run tests for fastapi
-      shell: bash
-      run: pipenv run pytest -m "teste2e" tests/gui --gui-server="fastapi"

+ 0 - 3
.github/actions/gui-test/prefix/action.yml

@@ -12,6 +12,3 @@ runs:
     - name: run pytest with prefix configuration
       shell: bash
       run: pipenv run pytest -m "teste2e" tests/gui/e2e --e2e-base-url="/prefix/" --e2e-port="4000"
-    - name: run pytest with prefix configuration with fastapi
-      shell: bash
-      run: pipenv run pytest -m "teste2e" tests/gui/e2e --e2e-base-url="/prefix/" --e2e-port="4000" --gui-server="fastapi"

+ 1 - 0
pytest.ini

@@ -11,3 +11,4 @@ markers =
     teste2e:End-to-end tests
     orchestrator_dispatcher:Orchestrator dispatcher tests
     standalone:Tests starting a standalone dispatcher thread
+    skip_if_not_server:Skip test if not running in specific server mode

+ 0 - 3
taipy/gui/.gitignore

@@ -48,9 +48,6 @@ demo*/
 docker-compose-dev*.yml
 Dockerfile.dev
 
-# Ignore jupyter notebook files except the simple_gui.ipynb
-!simple_gui.ipynb
-
 # GUI generation
 taipy_webapp/
 

+ 10 - 1
taipy/gui/gui.py

@@ -1089,7 +1089,7 @@ class Gui:
             os.makedirs(upload_path, exist_ok=True)
             # Save file into upload_path directory
             file_path = _get_non_existent_file_path(upload_path, secure_filename(file.filename))
-            file.save(os.path.join(upload_path, (file_path.name + suffix)))
+            self._server.save_uploaded_file(file, os.path.join(upload_path, (file_path.name + suffix)))
         else:
             _warn(f"upload files: Path {path} points outside of upload root.")
             return HttpResponse("upload files: Path part points outside of upload root.", 400)
@@ -2955,6 +2955,15 @@ class Gui:
         )
         fastapi_router.append(images_router)
 
+        upload_router = APIRouter()
+        upload_router.add_api_route(
+            f"/{Gui.__UPLOAD_URL}",
+            self.__upload_files,
+            methods=["POST"],
+            dependencies=[Depends(request_dependency), Depends(request_meta_dependency)],
+        )
+        fastapi_router.append(upload_router)
+
         user_content_router = APIRouter()
         user_content_router.add_api_route(
             f"/{Gui.__USER_CONTENT_URL}/{{path:path}}",

+ 50 - 10
taipy/gui/servers/fastapi/server.py

@@ -15,7 +15,8 @@ import threading
 import time
 import typing as t
 import webbrowser
-from contextlib import contextmanager
+from asyncio import Queue
+from contextlib import asynccontextmanager, contextmanager
 
 import socketio
 import uvicorn
@@ -24,6 +25,7 @@ from fastapi.encoders import jsonable_encoder
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import HTMLResponse, JSONResponse
 from fastapi.templating import Jinja2Templates
+from fastapi.testclient import TestClient
 from flask.ctx import _AppCtxGlobals
 from starlette.middleware.base import BaseHTTPMiddleware
 
@@ -38,7 +40,7 @@ from ..server import _Server
 from .request import request as request_context
 from .request import request_meta
 from .request import sid as sid_context
-from .utils import exec_async, send_from_directory
+from .utils import run_async, send_from_directory
 
 if t.TYPE_CHECKING:
     from ...gui import Gui
@@ -59,24 +61,27 @@ async def request_dependency(request: Request):
 # this is for ws calls as contextmanager
 @contextmanager
 def get_request_meta_ctx():
+    prev_meta = request_meta.get()
     new_request_meta = _AppCtxGlobals()
     request_meta.set(new_request_meta)
     yield new_request_meta
-    request_meta.set(None)
+    request_meta.set(prev_meta)
 
 
 @contextmanager
-def set_request_ctx(request: Request):
-    request_context.set(request)
+def set_sid_ctx(sid: str):
+    prev_sid = sid_context.get()
+    sid_context.set(sid)
     yield
-    request_context.set(None)
+    sid_context.set(prev_sid)
 
 
 @contextmanager
-def set_sid_ctx(sid: str):
-    sid_context.set(sid)
+def set_request_ctx(request: Request):
+    prev_request = request_context.get()
+    request_context.set(request)
     yield
-    sid_context.set(None)
+    request_context.set(prev_request)
 
 
 class CleanupMiddleware(BaseHTTPMiddleware):
@@ -106,6 +111,7 @@ class FastAPIServer(_Server):
         self._port: t.Optional[int] = None
 
         # server setup
+        self._emit_queue: Queue[t.Tuple[t.Tuple[t.Any, ...], t.Dict[str, t.Any]]] = Queue()
         self._server = server or FastAPI(json_encoder=_TaipyJsonEncoder)
         self._ws = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
         self._server.mount("/socket.io", socketio.ASGIApp(self._ws, other_asgi_app=self._server, socketio_path="/"))
@@ -160,6 +166,21 @@ class FastAPIServer(_Server):
 
         taipy_router = APIRouter()
 
+        async def emit_dispatcher():
+            while True:
+                emit_args, emit_kwargs = await self._emit_queue.get()
+                try:
+                    await self._ws.emit(*emit_args, **emit_kwargs)
+                except Exception as e:
+                    _TaipyLogger._get_logger().info("Error sending ws message: ", e)
+
+        @asynccontextmanager
+        async def lifespan(app: FastAPI):
+            self._ws.start_background_task(emit_dispatcher)
+            yield
+
+        taipy_router.lifespan_context = lifespan
+
         @taipy_router.get("/", response_class=HTMLResponse)
         @taipy_router.get("/{path:path}", response_class=HTMLResponse)
         def my_index(request: Request, path: str = "", request_meta: _AppCtxGlobals = Depends(request_meta_dependency)):  # noqa: B008
@@ -266,13 +287,32 @@ class FastAPIServer(_Server):
     def get_port(self) -> int:
         return self._port or -1
 
+    def test_client(self):
+        return TestClient(self._server)
+
+    def test_request_context(self, path, data):
+        return get_request_meta_ctx()
+
     def send_ws_message(self, *args, **kwargs):
+        if kwargs.get("to") is None:
+            kwargs["to"] = [sid_context.get()]
         if isinstance(kwargs["to"], str) or kwargs["to"] is None:
             kwargs["to"] = [kwargs["to"]]
+        if "include_self" in kwargs and kwargs["include_self"]:
+            del kwargs["include_self"]
+            sid = sid_context.get()
+            if sid is not None and None not in kwargs["to"] and sid not in kwargs["to"]:
+                kwargs["to"].append(sid)
         for sid in kwargs["to"]:
             temp_kwargs = kwargs.copy()
             temp_kwargs["to"] = sid
-            exec_async(self._ws.emit, "message", *args, **temp_kwargs)
+            self._emit_queue.put_nowait((("message", *args), temp_kwargs))
+
+    def save_uploaded_file(self, file, path):
+        if not file or not path:
+            raise ValueError("File and path must be provided.")
+        with open(path, "wb") as f_buffer:
+            f_buffer.write(run_async(file.read))
 
     def run(
         self,

+ 6 - 0
taipy/gui/servers/fastapi/utils.py

@@ -36,6 +36,12 @@ def send_from_directory(
         return Response("File not found", status_code=404)
     if not os.path.exists(joined_path) or not os.path.isfile(joined_path):
         return Response("File not found", status_code=404)
+    if "as_attachment" in kwargs:
+        if kwargs["as_attachment"]:
+            kwargs["filename"] = os.path.basename(path)
+            kwargs["media_type"] = "application/octet-stream"
+            kwargs["headers"] = {"Content-Disposition": f"attachment; filename={os.path.basename(path)}"}
+        del kwargs["as_attachment"]
     return FileResponse(joined_path, **kwargs)
 
 

+ 11 - 0
taipy/gui/servers/flask/server.py

@@ -19,6 +19,7 @@ import sys
 import time
 import typing as t
 import webbrowser
+from contextlib import contextmanager
 from importlib import util
 
 from flask import (
@@ -226,6 +227,13 @@ class FlaskServer(_Server):
     def test_client(self):
         return t.cast(Flask, self._server).test_client()
 
+    @contextmanager
+    def test_request_context(self, path, data=None):
+        if not isinstance(self._server, Flask):
+            raise RuntimeError("Flask server is not initialized")
+        with self._server.test_request_context(path, data=data):
+            yield
+
     def _run_notebook(self):
         self._is_running = True
         self._ws.run(self._server, host=self._host, port=self._port, debug=False, use_reloader=False)
@@ -249,6 +257,9 @@ class FlaskServer(_Server):
     def send_ws_message(self, *args, **kwargs):
         self._ws.emit("message", *args, **kwargs)
 
+    def save_uploaded_file(self, file, path):
+        file.save(path)
+
     def run(
         self,
         host,

+ 5 - 1
taipy/gui/servers/request.py

@@ -61,7 +61,11 @@ class RequestAccessor:
         if get_server_type() == "flask":
             return flask_request.files
         elif get_server_type() == "fastapi":
-            raise NotImplementedError("FastAPI does not support files handling yet")
+            fastapi_r = fastapi_request.get()
+            if fastapi_r is None:
+                return {}
+            form_data = dict(run_async(fastapi_r._get_form))
+            return {k: v for k, v in form_data.items() if hasattr(v, "filename")}
         return {}
 
     @staticmethod

+ 14 - 0
taipy/gui/servers/server.py

@@ -14,6 +14,7 @@ import pathlib
 import re
 import typing as t
 from abc import ABC, abstractmethod
+from contextlib import contextmanager
 from random import choices, randint
 
 from gitignore_parser import parse_gitignore
@@ -79,6 +80,10 @@ class _Server(ABC):
     def direct_render_json(self, data):
         raise NotImplementedError
 
+    @abstractmethod
+    def save_uploaded_file(self, file, path):
+        raise NotImplementedError
+
     def render(self, html_fragment, script_paths, style, head, context):
         template_str = _Server._RE_OPENING_CURLY.sub(_Server._OPENING_CURLY, html_fragment)
         template_str = _Server._RE_CLOSING_CURLY.sub(_Server._CLOSING_CURLY, template_str)
@@ -113,6 +118,15 @@ class _Server(ABC):
     ):
         raise NotImplementedError
 
+    @abstractmethod
+    def test_client(self):
+        raise NotImplementedError
+
+    @abstractmethod
+    @contextmanager
+    def test_request_context(self, path, data):
+        raise NotImplementedError
+
     @abstractmethod
     def run(
         self,

+ 3 - 5
tests/conftest.py

@@ -30,7 +30,6 @@ def pytest_addoption(parser: pytest.Parser) -> None:
     """Add custom command line options for pytest."""
     parser.addoption("--e2e-base-url", action="store", default="/", help="base url for e2e testing")
     parser.addoption("--e2e-port", action="store", default="5000", help="port for e2e testing")
-    parser.addoption("--gui-server", action="store", default="flask", help="server for e2e testing")
 
 
 @pytest.fixture(scope="session")
@@ -45,10 +44,9 @@ def e2e_port(request: pytest.FixtureRequest) -> str:
     return request.config.getoption("--e2e-port")
 
 
-@pytest.fixture(scope="session")
-def gui_server(request: pytest.FixtureRequest) -> str:
-    """Fixture to get the server for e2e testing."""
-    return request.config.getoption("--gui-server")
+@pytest.fixture(params=["flask", "fastapi"])
+def gui_server(request):
+    return request.param
 
 
 def remove_subparser(name: str) -> None:

+ 3 - 4
tests/gui/actions/test_actions.py

@@ -10,7 +10,6 @@
 # specific language governing permissions and limitations under the License.
 
 import inspect
-import json
 
 from taipy.gui import Gui, Markdown
 
@@ -24,12 +23,12 @@ def test_actions(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|action button|button|on_action={an_action}|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    response = flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    response = server_client.get(f"/taipy-jsx/test?client_id={cid}")
     assert response.status_code == 200, f"response.status_code {response.status_code} != 200"
-    response_data = json.loads(response.get_data().decode("utf-8", "ignore"))
+    response_data = helpers.get_response_data(response)
     assert isinstance(response_data, dict), "response_data is not Dict"
     assert "jsx" in response_data, "jsx not in response_data"
     jsx = response_data["jsx"]

+ 73 - 10
tests/gui/actions/test_download.py

@@ -10,15 +10,16 @@
 # specific language governing permissions and limitations under the License.
 
 import inspect
-import typing as t
 import warnings
 
-from flask import Flask
+import pytest
 
 from taipy.gui import Gui, Markdown, State, download
 from taipy.gui.servers import get_request_meta
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_download(gui: Gui, helpers):
     def on_download_action(state: State):
         pass
@@ -28,12 +29,12 @@ def test_download(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
-    ws_client = gui._server._ws.test_client(t.cast(Flask, gui._server.get_server_instance()))
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
         download(gui._Gui__state, "some text", "filename.txt", "on_download_action")  # type: ignore[attr-defined]
 
@@ -43,6 +44,7 @@ def test_download(gui: Gui, helpers):
     )
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_download_fn(gui: Gui, helpers):
     def on_download_action(state: State):
         pass
@@ -52,12 +54,12 @@ def test_download_fn(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
-    ws_client = gui._server._ws.test_client(t.cast(Flask, gui._server.get_server_instance()))
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
         download(gui._Gui__state, "some text", "filename.txt", on_download_action)  # type: ignore[attr-defined]
 
@@ -70,6 +72,67 @@ def test_download_fn(gui: Gui, helpers):
     assert "onAction" in received_messages[0]["args"]  # inner function is treated as lambda
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_download_fastapi(gui: Gui, helpers):
+    def on_download_action(state: State):
+        pass
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|Hello|button|>"))
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    cid = helpers.create_scope_and_get_sid(gui)
+    sid = ws_client.get_sid()
+    ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui.get_app_context():
+        RequestAccessor.set_sid(sid)
+        get_request_meta().client_id = cid
+        download(gui._Gui__state, "some text", "filename.txt", "on_download_action")  # type: ignore[attr-defined]
+
+    received_messages = ws_client.get_received()
+    try:
+        helpers.assert_outward_ws_simple_message(
+            received_messages[0], "DF", {"name": "filename.txt", "onAction": "on_download_action"}
+        )
+    finally:
+        ws_client.disconnect()
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_download_fn_fastapi(gui: Gui, helpers):
+    def on_download_action(state: State):
+        pass
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|Hello|button|>"))
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    cid = helpers.create_scope_and_get_sid(gui)
+    sid = ws_client.get_sid()
+    ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui.get_app_context():
+        RequestAccessor.set_sid(sid)
+        get_request_meta().client_id = cid
+        download(gui._Gui__state, "some text", "filename.txt", on_download_action)  # type: ignore[attr-defined]
+
+    received_messages = ws_client.get_received()
+    try:
+        helpers.assert_outward_ws_simple_message(
+            received_messages[0],
+            "DF",
+            {"name": "filename.txt", "context": "test_download"},
+        )
+        assert "onAction" in received_messages[0]["args"]  # inner function is treated as lambda
+    finally:
+        ws_client.disconnect()
+
+
 def test_bad_download(gui: Gui, helpers):
     with warnings.catch_warnings(record=True) as records:
         download(None, "some text", "filename.txt", "on_download_action")  # type: ignore[arg-type]

+ 4 - 4
tests/gui/actions/test_get_module_context.py

@@ -22,9 +22,9 @@ def test_get_module_context(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     with gui.get_app_context():
         get_request_meta().client_id = cid
         module = get_module_context(gui._Gui__state)  # type: ignore[attr-defined]
@@ -43,9 +43,9 @@ def test_get_module_name_from_state(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     with gui.get_app_context():
         get_request_meta().client_id = cid
         module = get_module_name_from_state(gui._Gui__state)  # type: ignore[attr-defined]

+ 2 - 2
tests/gui/actions/test_get_state_id.py

@@ -22,9 +22,9 @@ def test_get_state_id(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     with gui.get_app_context():
         get_request_meta().client_id = cid
         assert cid == get_state_id(gui._Gui__state)  # type: ignore[attr-defined]

+ 3 - 3
tests/gui/actions/test_get_user_content_url.py

@@ -22,11 +22,11 @@ def test_get_content_url(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
         url = get_user_content_url(gui._Gui__state, "path")  # type: ignore[attr-defined]
         assert url == "/taipy-user-content/path?client_id=test"

+ 33 - 3
tests/gui/actions/test_hold_control.py

@@ -12,22 +12,26 @@
 import inspect
 import warnings
 
+import pytest
+
 from taipy.gui import Gui, Markdown, hold_control
 from taipy.gui.servers import get_request_meta
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_hold_control(gui: Gui, helpers):
     # set gui frame
     gui._set_frame(inspect.currentframe())
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
         hold_control(gui._Gui__state)  # type: ignore[attr-defined]
 
@@ -37,6 +41,32 @@ def test_hold_control(gui: Gui, helpers):
     )
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_hold_control_fastapi(gui: Gui, helpers):
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|Hello|button|>"))
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    cid = helpers.create_scope_and_get_sid(gui)
+    sid = ws_client.get_sid()
+    ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui.get_app_context():
+        RequestAccessor.set_sid(sid)
+        get_request_meta().client_id = cid
+        hold_control(gui._Gui__state)  # type: ignore[attr-defined]
+
+    received_messages = ws_client.get_received()
+    try:
+        helpers.assert_outward_ws_simple_message(
+            received_messages[0], "BL", {"action": "_taipy_on_cancel_block_ui", "message": "Work in Progress..."}
+        )
+    finally:
+        ws_client.disconnect()
+
+
 def test_bad_hold_control(gui: Gui, helpers):
     with warnings.catch_warnings(record=True) as records:
         hold_control(None)  # type: ignore[arg-type]

+ 5 - 4
tests/gui/actions/test_invoke_callback.py

@@ -35,11 +35,11 @@ def test_invoke_callback(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello|button|>\n<|{val}|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # client id
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
 
     gui.invoke_callback(cid, user_callback, [])
     with get_state(gui, cid) as state:
@@ -57,15 +57,16 @@ def test_invoke_callback_sid(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello|button|>\n<|{val}|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # client id
     cid = helpers.create_scope_and_get_sid(gui)
     base_sid, _ = gui._bindings()._get_or_create_scope("base sid")
 
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     with gui.get_app_context():
         get_request_meta().client_id = base_sid
+        assert get_request_meta().client_id == base_sid
         gui.invoke_callback(cid, user_callback, [])
         assert get_request_meta().client_id == base_sid
 

+ 33 - 5
tests/gui/actions/test_navigate.py

@@ -12,22 +12,26 @@
 import inspect
 import warnings
 
+import pytest
+
 from taipy.gui import Gui, Markdown, navigate
 from taipy.gui.servers import get_request_meta
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_navigate(gui: Gui, helpers):
     # set gui frame
     gui._set_frame(inspect.currentframe())
 
-    gui.add_page("test", Markdown("<|Hello|button|>"))
+    gui.add_page("test", Markdown("<|hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
-    # WS client and emit
+    server_client = gui._server.test_client()
+    # ws client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
         navigate(gui._Gui__state, "test")  # type: ignore[attr-defined]
 
@@ -35,6 +39,30 @@ def test_navigate(gui: Gui, helpers):
     helpers.assert_outward_ws_simple_message(received_messages[0], "NA", {"to": "test"})
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_navigate_fastapi(gui: Gui, helpers):
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|hello|button|>"))
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    cid = helpers.create_scope_and_get_sid(gui)
+    sid = ws_client.get_sid()
+    ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui.get_app_context():
+        RequestAccessor.set_sid(sid)
+        get_request_meta().client_id = cid
+        navigate(gui._Gui__state, "test")  # type: ignore[attr-defined]
+
+    try:
+        received_messages = ws_client.get_received()
+        helpers.assert_outward_ws_simple_message(received_messages[0], "NA", {"to": "test"})
+    finally:
+        ws_client.disconnect()
+
+
 def test_bad_navigate(gui: Gui, helpers):
     with warnings.catch_warnings(record=True) as records:
         navigate(None, "test")  # type: ignore[arg-type]

+ 72 - 12
tests/gui/actions/test_notify.py

@@ -12,54 +12,86 @@
 import inspect
 import warnings
 
+import pytest
+
 from taipy.gui import Gui, Markdown, close_notification, notify
 from taipy.gui.servers import get_request_meta
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_notify(gui: Gui, helpers):
     # set gui frame
     gui._set_frame(inspect.currentframe())
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
-        id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
-        assert id == "id"
+        notify_id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
+        assert notify_id == "id"
     received_messages = ws_client.get_received()
     helpers.assert_outward_ws_simple_message(
         received_messages[0], "AL", {"nType": "Info", "message": "Message", "notificationId": "id"}
     )
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_notify_fastapi(gui: Gui, helpers):
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|Hello|button|>"))
+    gui.run(run_server=False)
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    cid = helpers.create_scope_and_get_sid(gui)
+    sid = ws_client.get_sid()
+    ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui.get_app_context():
+        RequestAccessor.set_sid(sid)
+        get_request_meta().client_id = cid
+        notify_id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
+        assert notify_id == "id"
+    received_messages = ws_client.get_received()
+    try:
+        helpers.assert_outward_ws_simple_message(
+            received_messages[0], "AL", {"nType": "Info", "message": "Message", "notificationId": "id"}
+        )
+    finally:
+        ws_client.disconnect()
+
+
 def test_bad_notify(gui: Gui, helpers):
     with warnings.catch_warnings(record=True) as records:
-        id = notify(None, "Info", "Message", id="id")  # type: ignore[arg-type]
-        assert id is None
+        notify_id = notify(None, "Info", "Message", id="id")  # type: ignore[arg-type]
+        assert notify_id is None
         assert len(records) == 1
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_close_notification(gui: Gui, helpers):
     # set gui frame
     gui._set_frame(inspect.currentframe())
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
-        id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
-        close_notification(gui._Gui__state, id)  # type: ignore[attr-defined, arg-type]
+        notify_id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
+        close_notification(gui._Gui__state, notify_id)  # type: ignore[attr-defined, arg-type]
     received_messages = ws_client.get_received()
     helpers.assert_outward_ws_simple_message(
         received_messages[0], "AL", {"nType": "Info", "message": "Message", "notificationId": "id"}
@@ -67,6 +99,34 @@ def test_close_notification(gui: Gui, helpers):
     helpers.assert_outward_ws_simple_message(received_messages[1], "AL", {"nType": "", "notificationId": "id"})
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_close_notification_fastapi(gui: Gui, helpers):
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|Hello|button|>"))
+    gui.run(run_server=False)
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    cid = helpers.create_scope_and_get_sid(gui)
+    sid = ws_client.get_sid()
+    ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui.get_app_context():
+        RequestAccessor.set_sid(sid)
+        get_request_meta().client_id = cid
+        notify_id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
+        close_notification(gui._Gui__state, notify_id)  # type: ignore[attr-defined, arg-type]
+    received_messages = ws_client.get_received()
+    try:
+        helpers.assert_outward_ws_simple_message(
+            received_messages[0], "AL", {"nType": "Info", "message": "Message", "notificationId": "id"}
+        )
+        helpers.assert_outward_ws_simple_message(received_messages[1], "AL", {"nType": "", "notificationId": "id"})
+    finally:
+        ws_client.disconnect()
+
+
 def test_bad_close_notification(gui: Gui, helpers):
     with warnings.catch_warnings(record=True) as records:
         close_notification(None, "id")  # type: ignore[arg-type]

+ 31 - 3
tests/gui/actions/test_resume_control.py

@@ -12,22 +12,26 @@
 import inspect
 import warnings
 
+import pytest
+
 from taipy.gui import Gui, Markdown, resume_control
 from taipy.gui.servers import get_request_meta
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_resume_control(gui: Gui, helpers):
     # set gui frame
     gui._set_frame(inspect.currentframe())
 
     gui.add_page("test", Markdown("<|Hello|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
         resume_control(gui._Gui__state)  # type: ignore[attr-defined]
 
@@ -35,6 +39,30 @@ def test_resume_control(gui: Gui, helpers):
     helpers.assert_outward_ws_simple_message(received_messages[0], "BL", {"message": None})
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_resume_control_fastapi(gui: Gui, helpers):
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|Hello|button|>"))
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    cid = helpers.create_scope_and_get_sid(gui)
+    sid = ws_client.get_sid()
+    ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui.get_app_context():
+        RequestAccessor.set_sid(sid)
+        get_request_meta().client_id = cid
+        resume_control(gui._Gui__state)  # type: ignore[attr-defined]
+
+    received_messages = ws_client.get_received()
+    try:
+        helpers.assert_outward_ws_simple_message(received_messages[0], "BL", {"message": None})
+    finally:
+        ws_client.disconnect()
+
+
 def test_bad_resume_control(gui: Gui, helpers):
     with warnings.catch_warnings(record=True) as records:
         resume_control(None)  # type: ignore[arg-type]

+ 9 - 2
tests/gui/conftest.py

@@ -36,10 +36,10 @@ def small_dataframe():
 
 
 @pytest.fixture(scope="function")
-def gui(helpers):
+def gui(helpers, gui_server):
     from taipy.gui import Gui
 
-    gui = Gui()
+    gui = Gui(server=gui_server)
     yield gui
     # Delete Gui instance and state of some classes after each test
     gui.stop()
@@ -72,3 +72,10 @@ def test_client():
 def patch_cli_args():
     with patch("sys.argv", ["prog"]):
         yield
+
+
+@pytest.fixture(autouse=True)
+def skip_if_gui_server_is_not(request, gui_server):
+    skip_marker = request.node.get_closest_marker("skip_if_not_server")
+    if skip_marker and skip_marker.args[0] != gui_server:
+        pytest.skip(f"Skipped because gui_server is not {gui_server}")

+ 14 - 14
tests/gui/content_provider/test_content_provider.py

@@ -59,12 +59,12 @@ def test_process_content_provider(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello {str(an_instance)}|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     # my content provider
-    result = flask_client.get(
+    result = server_client.get(
         f"/taipy-user-content/test?client_id={cid}&__taipy_html_content=true&variable_name=an_instance"
     )
     assert "instance of <class 'test_content_provider._AType'>" in result.data.decode()
@@ -78,15 +78,15 @@ def test_process_content_provider_invalid(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello {v_name}|button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     # no content provider
-    result = flask_client.get(
+    result = server_client.get(
         f"/taipy-user-content/test?client_id={cid}&__taipy_html_content=true&variable_name=v_name"
     )
-    assert "No valid provider" in result.data.decode()
+    assert "No valid provider" in helpers.get_response_raw_data(result)
 
 
 def test__serve_user_content(gui: Gui, helpers):
@@ -98,13 +98,13 @@ def test__serve_user_content(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello |button|on_action=user_content|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     # no content provider
-    result = flask_client.get(f"/taipy-user-content/test?client_id={cid}&custom_user_content_cb=user_content")
-    assert "taipy.gui.state._GuiState" in result.data.decode()
+    result = server_client.get(f"/taipy-user-content/test?client_id={cid}&custom_user_content_cb=user_content")
+    assert "taipy.gui.state._GuiState" in helpers.get_response_raw_data(result)
 
 
 def test__serve_user_content_bad(gui: Gui, helpers):
@@ -113,11 +113,11 @@ def test__serve_user_content_bad(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello |button|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     # no content provider
     with warnings.catch_warnings(record=True) as records:
-        flask_client.get(f"/taipy-user-content/test?client_id={cid}&custom_user_content_cb=bad_user_content")
+        server_client.get(f"/taipy-user-content/test?client_id={cid}&custom_user_content_cb=bad_user_content")
         assert len(records) == 2

+ 6 - 6
tests/gui/data/test_pandas_data_accessor.py

@@ -104,7 +104,7 @@ def test_style(gui: Gui, helpers, small_dataframe):
     pd = pandas.DataFrame(data=small_dataframe)
     gui.run(run_server=False)
     cid = helpers.create_scope_and_get_sid(gui)
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
         value = accessor.get_data("x", pd, {"start": 0, "end": 1, "styles": {"st": "test_style"}}, _DataFormat.JSON)[
             "value"
@@ -123,7 +123,7 @@ def test_tooltip(gui: Gui, helpers, small_dataframe):
     pd = pandas.DataFrame(data=small_dataframe)
     gui.run(run_server=False)
     cid = helpers.create_scope_and_get_sid(gui)
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         gui._bind_var_val("tt", tt)
         gui._get_locals_bind_from_context(None)["tt"] = tt
         get_request_meta().client_id = cid
@@ -142,7 +142,7 @@ def test_format_fn(gui: Gui, helpers, small_dataframe):
     pd = pandas.DataFrame(data=small_dataframe)
     gui.run(run_server=False)
     cid = helpers.create_scope_and_get_sid(gui)
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         gui._bind_var_val("ff", ff)
         gui._get_locals_bind_from_context(None)["ff"] = ff
         get_request_meta().client_id = cid
@@ -342,12 +342,12 @@ def test_decimator(gui: Gui, helpers, small_dataframe):
 
     gui.add_page("test", "<|Hello {a_decimator}|button|>")
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
 
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         get_request_meta().client_id = cid
 
         ret_data = accessor.get_data(

+ 26 - 1
tests/gui/gui_specific/test_favicon.py

@@ -12,9 +12,13 @@
 import inspect
 import warnings
 
+import pytest
+
 from taipy.gui import Gui, Markdown
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_favicon(gui: Gui, helpers):
     with warnings.catch_warnings(record=True):
         gui._set_frame(inspect.currentframe())
@@ -29,6 +33,27 @@ def test_favicon(gui: Gui, helpers):
         gui.set_favicon("https://newfavicon.com/favicon.png")
         # assert for received message (message that would be sent to the front-end client)
         msgs = ws_client.get_received()
-        assert msgs
+        assert msgs is not None
         assert msgs[0].get("args", {}).get("type", None) == "FV"
         assert msgs[0].get("args", {}).get("payload", {}).get("value", None) == "https://newfavicon.com/favicon.png"
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_favicon_fastapi(gui: Gui, helpers):
+    with warnings.catch_warnings(record=True):
+        gui._set_frame(inspect.currentframe())
+        gui.add_page("test", Markdown("#This is a page"))
+        helpers.run_e2e_multi_client(gui)
+        ws_client = helpers.get_socketio_test_client()
+        sid = helpers.create_scope_and_get_sid(gui)
+        RequestAccessor.set_sid(ws_client.get_sid())
+        ws_client.get(f"/taipy-jsx/test?client_id={sid}")
+        gui.set_favicon("https://newfavicon.com/favicon.png")
+        msgs = ws_client.get_received()
+        try:
+            assert len(msgs) > 0
+            assert msgs[0].get("args", {}).get("type", None) == "FV"
+            assert msgs[0].get("args", {}).get("payload", {}).get("value", None) == "https://newfavicon.com/favicon.png"
+        finally:
+            ws_client.disconnect()

+ 7 - 8
tests/gui/gui_specific/test_multiple_instances.py

@@ -9,25 +9,24 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 
-import json
 
 from taipy.gui.gui import Gui
 
 
-def test_multiple_instance():
-    gui1 = Gui("<|gui1|>")
-    gui2 = Gui("<|gui2|>")
+def test_multiple_instance(gui_server, helpers):
+    gui1 = Gui("<|gui1|>", server=gui_server)
+    gui2 = Gui("<|gui2|>", server=gui_server)
     gui1.run(run_server=False)
     gui2.run(run_server=False)
     client1 = gui1._server.test_client()
     client2 = gui2._server.test_client()
-    assert_multiple_instance(client1, 'value="gui1"')
-    assert_multiple_instance(client2, 'value="gui2"')
+    assert_multiple_instance(client1, helpers, 'value="gui1"')
+    assert_multiple_instance(client2, helpers, 'value="gui2"')
 
 
-def assert_multiple_instance(client, expected_value):
+def assert_multiple_instance(client, helpers, expected_value):
     response = client.get("/taipy-jsx/TaiPy_root_page")
-    response_data = json.loads(response.get_data().decode("utf-8", "ignore"))
+    response_data = helpers.get_response_data(response)
     assert response.status_code == 200
     assert isinstance(response_data, dict)
     assert "jsx" in response_data

+ 42 - 0
tests/gui/gui_specific/test_navigate.py

@@ -12,9 +12,12 @@
 import inspect
 import warnings
 
+import pytest
+
 from taipy.gui import Gui, Markdown, State, navigate
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_navigate(gui: Gui, helpers):
     def navigate_to(state: State):
         navigate(state, "test")
@@ -34,6 +37,26 @@ def test_navigate(gui: Gui, helpers):
         assert ws_client.get_received()
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_navigate_fastapi(gui: Gui, helpers):
+    def navigate_to(state: State):
+        navigate(state, "test")
+
+    with warnings.catch_warnings(record=True):
+        gui._set_frame(inspect.currentframe())
+        gui.add_page("test", Markdown("#This is a page"))
+        helpers.run_e2e_multi_client(gui)
+        ws_client = helpers.get_socketio_test_client()
+        cid = helpers.create_scope_and_get_sid(gui)
+        sid = ws_client.get_sid()
+        ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+        ws_client.emit("message", {"client_id": sid, "type": "A", "name": "my_button", "payload": "navigate_to"})
+        # assert for received message (message that would be sent to the front-end client)
+        assert ws_client.get_received()
+
+
+@pytest.mark.skip_if_not_server("flask")
 def test_navigate_to_no_route(gui: Gui, helpers):
     def navigate_to(state: State):
         navigate(state, "toto")
@@ -53,6 +76,25 @@ def test_navigate_to_no_route(gui: Gui, helpers):
         assert not ws_client.get_received()
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_navigate_to_no_route_fastapi(gui: Gui, helpers):
+    def navigate_to(state: State):
+        navigate(state, "toto")
+
+    with warnings.catch_warnings(record=True):
+        gui._set_frame(inspect.currentframe())
+        gui.add_page("test", Markdown("#This is a page"))
+        helpers.run_e2e_multi_client(gui)
+        ws_client = helpers.get_socketio_test_client()
+        cid = helpers.create_scope_and_get_sid(gui)
+        sid = ws_client.get_sid()
+        ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+        ws_client.emit("message", {"client_id": sid, "type": "A", "name": "my_button", "payload": "navigate_to"})
+        # assert for received message (message that would be sent to the front-end client)
+        assert not ws_client.get_received()
+
+
 def test_on_navigate_to_inexistant(gui: Gui, helpers):
     def on_navigate(state: State, page: str):
         return "test2" if page == "test" else page

+ 5 - 6
tests/gui/gui_specific/test_partial.py

@@ -9,31 +9,30 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 
-import json
 import warnings
 from types import SimpleNamespace
 
 from taipy.gui import Gui, Markdown
 
 
-def test_partial(gui: Gui):
+def test_partial(gui: Gui, helpers):
     with warnings.catch_warnings(record=True):
         gui.add_partial(Markdown("#This is a partial"))
         gui.run(run_server=False)
         client = gui._server.test_client()
         response = client.get(f"/taipy-jsx/{gui._config.partial_routes[0]}")
-        response_data = json.loads(response.get_data().decode("utf-8", "ignore"))
+        response_data = helpers.get_response_data(response)
         assert response.status_code == 200
         assert "jsx" in response_data and "This is a partial" in response_data["jsx"]
 
 
-def test_partial_update(gui: Gui):
+def test_partial_update(gui: Gui, helpers):
     with warnings.catch_warnings(record=True):
         partial = gui.add_partial(Markdown("#This is a partial"))
         gui.run(run_server=False, single_client=True)
         client = gui._server.test_client()
         response = client.get(f"/taipy-jsx/{gui._config.partial_routes[0]}")
-        response_data = json.loads(response.get_data().decode("utf-8", "ignore"))
+        response_data = helpers.get_response_data(response)
         assert response.status_code == 200
         assert "jsx" in response_data and "This is a partial" in response_data["jsx"]
         # update partial
@@ -41,6 +40,6 @@ def test_partial_update(gui: Gui):
         fake_state._gui = gui
         partial.update_content(fake_state, "#partial updated")  # type: ignore
         response = client.get(f"/taipy-jsx/{gui._config.partial_routes[0]}")
-        response_data = json.loads(response.get_data().decode("utf-8", "ignore"))
+        response_data = helpers.get_response_data(response)
         assert response.status_code == 200
         assert "jsx" in response_data and "partial updated" in response_data["jsx"]

+ 2 - 3
tests/gui/gui_specific/test_render_route.py

@@ -10,13 +10,12 @@
 # specific language governing permissions and limitations under the License.
 
 import inspect
-import json
 import warnings
 
 from taipy.gui import Gui
 
 
-def test_render_route(gui: Gui):
+def test_render_route(gui: Gui, helpers):
     gui._set_frame(inspect.currentframe())
     gui.add_page("page1", "# first page")
     gui.add_page("page2", "# second page")
@@ -24,7 +23,7 @@ def test_render_route(gui: Gui):
     with warnings.catch_warnings(record=True):
         client = gui._server.test_client()
         response = client.get("/taipy-init")
-        response_data = json.loads(response.get_data().decode("utf-8", "ignore"))
+        response_data = helpers.get_response_data(response)
         assert response.status_code == 200
         assert isinstance(response_data, dict)
         assert isinstance(response_data["locations"], dict)

+ 12 - 9
tests/gui/gui_specific/test_variable_binding.py

@@ -13,7 +13,7 @@
 from taipy.gui import Gui, Markdown
 
 
-def test_variable_binding(helpers):
+def test_variable_binding(helpers, gui_server):
     """
     Tests the binding of a few variables and a function
     """
@@ -24,11 +24,12 @@ def test_variable_binding(helpers):
     x = 10
     y = 20
     z = "button label"
-    gui = Gui()
+    gui = Gui(server=gui_server)
     gui.add_page("test", Markdown("<|{x}|> | <|{y}|> | <|{z}|button|on_action=another_function|>"))
     gui.run(run_server=False, single_client=True)
     client = gui._server.test_client()
-    jsx = client.get("/taipy-jsx/test").json["jsx"]
+    response = client.get("/taipy-jsx/test")
+    jsx = helpers.get_response_data(response)["jsx"]
     for expected in ["<Button", 'defaultLabel="button label"', "label={tpec_TpExPr_z_TPMDL_0}"]:
         assert expected in jsx
     assert gui._bindings().x == x
@@ -39,28 +40,30 @@ def test_variable_binding(helpers):
     helpers.test_cleanup()
 
 
-def test_properties_binding(helpers):
-    gui = Gui()
+def test_properties_binding(helpers, gui_server):
+    gui = Gui(server=gui_server)
     modifier = "nice "  # noqa: F841
     button_properties = {"label": "A {modifier}button"}  # noqa: F841
     gui.add_page("test", Markdown("<|button|properties=button_properties|>"))
     gui.run(run_server=False)
     client = gui._server.test_client()
-    jsx = client.get("/taipy-jsx/test").json["jsx"]
+    response = client.get("/taipy-jsx/test")
+    jsx = helpers.get_response_data(response)["jsx"]
     for expected in ["<Button", 'defaultLabel="A nice button"']:
         assert expected in jsx
     helpers.test_cleanup()
 
 
-def test_dict_binding(helpers):
+def test_dict_binding(helpers, gui_server):
     """
     Tests the binding of a dictionary property
     """
     d = {"k": "test"}  # noqa: F841
-    gui = Gui("<|{d.k}|>")
+    gui = Gui("<|{d.k}|>", server=gui_server)
     gui.run(run_server=False)
     client = gui._server.test_client()
-    jsx = client.get("/taipy-jsx/TaiPy_root_page").json["jsx"]
+    response = client.get("/taipy-jsx/TaiPy_root_page")
+    jsx = helpers.get_response_data(response)["jsx"]
     for expected in ["<Field", 'defaultValue="test"']:
         assert expected in jsx
     helpers.test_cleanup()

+ 67 - 1
tests/gui/helpers.py

@@ -16,19 +16,65 @@ import time
 import typing as t
 import warnings
 
+import socketio
+import urllib3
+
 from taipy.gui import Gui, Html, Markdown
 from taipy.gui._renderers.builder import _Builder
 from taipy.gui._warnings import TaipyGuiWarning
+from taipy.gui.servers.utils import get_server_type
 from taipy.gui.utils._variable_directory import _reset_name_map
 from taipy.gui.utils.expr_var_name import _reset_expr_var_name
 
 
+class WebSocketTestClient:
+    def __init__(self, url: str = "http://localhost:5000", auto_connect=True):
+        self.url = url
+        self.sio = socketio.Client()
+        self.messages: t.List = []
+        # Register event handlers
+        self.sio.on("message", self._on_message)
+        # Auto connect if specified
+        if auto_connect:
+            self.connect()
+
+    def _on_message(self, data):
+        self.messages.append(data)
+
+    def connect(self):
+        self.sio.connect(f"{self.url}/socket.io")
+
+    def disconnect(self):
+        self.sio.disconnect()
+
+    def emit(self, *args, **kwargs):
+        self.sio.emit(*args, **kwargs)
+        self.sio.sleep(1)
+
+    def get_received(self, timeout=1):
+        self.sio.sleep(timeout)
+        # wrap the messages in a dict to match the expected format
+        wrapped_messages = []
+        wrapped_messages.extend({"name": "message", "args": message} for message in self.messages)
+        self.messages = []
+        return wrapped_messages
+
+    def get(self, url):
+        return urllib3.request("GET", self.url + url)
+
+    def get_sid(self) -> t.Optional[str]:
+        return self.sio.get_sid()
+
+
 class Helpers:
     @staticmethod
     def test_cleanup():
         _Builder._reset_key()
         _reset_name_map()
         _reset_expr_var_name()
+        Gui._Gui__extensions.clear()
+        Gui._Gui__shared_variables.clear()
+        Gui._Gui__content_providers.clear()
 
     @staticmethod
     def test_control_md(gui: Gui, md_string: str, expected_values: t.Union[str, t.List]):
@@ -51,7 +97,7 @@ class Helpers:
         client = gui._server.test_client()
         response = client.get("/taipy-jsx/test")
         assert response.status_code == 200, f"response.status_code {response.status_code} != 200"
-        response_data = json.loads(response.get_data().decode("utf-8", "ignore"))
+        response_data = Helpers.get_response_data(response)
         assert isinstance(response_data, t.Dict), "response_data is not Dict"
         assert "jsx" in response_data, "jsx not in response_data"
         jsx = response_data["jsx"]
@@ -166,3 +212,23 @@ class Helpers:
     @staticmethod
     def get_taipy_warnings(warns: t.List[warnings.WarningMessage]) -> t.List[warnings.WarningMessage]:
         return [w for w in warns if issubclass(w.category, TaipyGuiWarning)]
+
+    @staticmethod
+    def get_response_data(response):
+        if get_server_type() == "flask":
+            return json.loads(response.get_data().decode("utf-8", "ignore"))
+        elif get_server_type() == "fastapi":
+            return response.json()
+        return None
+
+    @staticmethod
+    def get_response_raw_data(response):
+        if get_server_type() == "flask":
+            return response.get_data().decode("utf-8", "ignore")
+        elif get_server_type() == "fastapi":
+            return response.text
+        return None
+
+    @staticmethod
+    def get_socketio_test_client():
+        return WebSocketTestClient()

+ 4 - 1
tests/gui/hooks/test_listener.py

@@ -27,6 +27,7 @@ def test_empty_listener(gui: Gui):
 
     listener.assert_not_called()
 
+
 def test_listener(gui: Gui):
     class ListenerHook(_Hook):
         method_names = ["_add_event_listener", "_fire_event"]
@@ -59,8 +60,10 @@ def test_listener(gui: Gui):
 
     gui._add_event_listener(event, listener)
 
-    with gui._Gui__event_manager: # type: ignore[attr-defined]
+    with gui._Gui__event_manager:  # type: ignore[attr-defined]
         gui._fire_event(event, None, payload)
 
     time.sleep(0.3)
     listener.assert_called_once_with(event, None, payload)
+
+    _Hooks()._Hooks__hooks.clear()

+ 3 - 0
tests/gui/notebook/.gitignore

@@ -0,0 +1,3 @@
+# Ignore jupyter notebook files except the simple_gui.ipynb
+!simple_gui_fastapi.ipynb
+!simple_gui.ipynb

+ 187 - 0
tests/gui/notebook/simple_gui_fastapi.ipynb

@@ -0,0 +1,187 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "fbd54fe1",
+   "metadata": {
+    "tags": [
+     "import"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "from taipy.gui import Gui, Markdown"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "301d6ab4",
+   "metadata": {
+    "tags": [
+     "page_declaration"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "a = 10\n",
+    "page = Markdown(\"# Hello\\n<|{a}|>\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "998cbb6f",
+   "metadata": {
+    "tags": [
+     "gui_init"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui = Gui(server=\"fastapi\")\n",
+    "gui.add_page(\"page1\", page)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "50d524c2",
+   "metadata": {
+    "tags": [
+     "gui_run"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui.run(run_browser=False)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7b722848",
+   "metadata": {
+    "tags": [
+     "get_variable"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui.state.a"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "12e0df89",
+   "metadata": {
+    "tags": [
+     "set_variable"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui.state.a = 20"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8332b3b5",
+   "metadata": {
+    "tags": [
+     "re_get_variable"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui.state.a"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6b9aecf6",
+   "metadata": {
+    "tags": [
+     "gui_stop"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui.stop()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9db9e339",
+   "metadata": {
+    "tags": [
+     "gui_re_run"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui.run(run_browser=False)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9a99cbb8",
+   "metadata": {
+    "tags": [
+     "gui_reload"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui.reload()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6fea38f8",
+   "metadata": {
+    "tags": [
+     "gui_re_stop"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "gui.stop()"
+   ]
+  }
+ ],
+ "metadata": {
+  "celltoolbar": "Tags",
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.3"
+  },
+  "vscode": {
+   "interpreter": {
+    "hash": "f81c1c330ff76e7e2c4accc0f953a67e4f184dd1f43b2c6b6dd2818d8aa11ecb"
+   }
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}

+ 45 - 0
tests/gui/notebook/test_notebook_simple_gui.py

@@ -17,6 +17,7 @@ import pytest
 from testbook import testbook
 
 
+@pytest.mark.skip_if_not_server("flask")
 @pytest.mark.filterwarnings("ignore::RuntimeWarning")
 @pytest.mark.teste2e
 @testbook("tests/gui/notebook/simple_gui.ipynb")
@@ -58,3 +59,47 @@ def test_notebook_simple_gui(tb, helpers):
     with pytest.raises(Exception) as exc_info:
         urlopen("http://127.0.0.1:5000/taipy-jsx/page1")
     assert "501: Gateway error" in str(exc_info.value)
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.filterwarnings("ignore::RuntimeWarning")
+@pytest.mark.teste2e
+@testbook("tests/gui/notebook/simple_gui_fastapi.ipynb")
+def test_notebook_simple_gui_fastapi(tb, helpers):
+    tb.execute_cell("import")
+    tb.execute_cell("page_declaration")
+    tb.execute_cell("gui_init")
+    tb.execute_cell("gui_run")
+    while not helpers.port_check():
+        time.sleep(0.1)
+    assert ">Hello</h1>" in urlopen("http://127.0.0.1:5000/taipy-jsx/page1").read().decode("utf-8")
+    assert 'defaultValue=\\"10\\"' in urlopen("http://127.0.0.1:5000/taipy-jsx/page1").read().decode("utf-8")
+    # Test state manipulation within notebook
+    tb.execute_cell("get_variable")
+    assert "10" in tb.cell_output_text("get_variable")
+    assert 'defaultValue=\\"10\\"' in urlopen("http://127.0.0.1:5000/taipy-jsx/page1").read().decode("utf-8")
+    tb.execute_cell("set_variable")
+    assert 'defaultValue=\\"20\\"' in urlopen("http://127.0.0.1:5000/taipy-jsx/page1").read().decode("utf-8")
+    tb.execute_cell("re_get_variable")
+    assert "20" in tb.cell_output_text("re_get_variable")
+    # Test page reload
+    tb.execute_cell("gui_stop")
+    with pytest.raises(Exception) as exc_info:
+        urlopen("http://127.0.0.1:5000/taipy-jsx/page1")
+    assert "Errno 61" in str(exc_info.value)
+    tb.execute_cell("gui_re_run")
+    while True:
+        with contextlib.suppress(Exception):
+            urlopen("http://127.0.0.1:5000/taipy-jsx/page1")
+            break
+    assert ">Hello</h1>" in urlopen("http://127.0.0.1:5000/taipy-jsx/page1").read().decode("utf-8")
+    tb.execute_cell("gui_reload")
+    while True:
+        with contextlib.suppress(Exception):
+            urlopen("http://127.0.0.1:5000/taipy-jsx/page1")
+            break
+    assert ">Hello</h1>" in urlopen("http://127.0.0.1:5000/taipy-jsx/page1").read().decode("utf-8")
+    tb.execute_cell("gui_re_stop")
+    with pytest.raises(Exception) as exc_info:
+        urlopen("http://127.0.0.1:5000/taipy-jsx/page1")
+    assert "Errno 61" in str(exc_info.value)

+ 2 - 1
tests/gui/renderers/test_html_parsing.py

@@ -21,5 +21,6 @@ def test_simple_html(gui: Gui, helpers):
     gui.add_page("test", Html(html_string))
     gui.run(run_server=False)
     client = gui._server.test_client()
-    jsx = client.get("/taipy-jsx/test").json["jsx"]
+    response = client.get("/taipy-jsx/test")
+    jsx = helpers.get_response_data(response)["jsx"]
     assert jsx == "<h1>test</h1>"

+ 4 - 4
tests/gui/server/http/test_extension.py

@@ -26,16 +26,16 @@ class MyLibrary(ElementLibrary):
 
 def test_extension_no_config(gui: Gui, helpers):
     gui.run(run_server=False, single_client=True)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     with pytest.warns(UserWarning):
-        ret = flask_client.get("/taipy-extension/toto/titi")
+        ret = server_client.get("/taipy-extension/toto/titi")
         assert ret.status_code == 404
 
 
 def test_extension_config_wrong_path(gui: Gui, helpers):
     Gui.add_library(MyLibrary())
     gui.run(run_server=False, single_client=True)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     with pytest.warns(UserWarning):
-        ret = flask_client.get("/taipy-extension/taipy_extension_example/titi")
+        ret = server_client.get("/taipy-extension/taipy_extension_example/titi")
         assert ret.status_code == 404

+ 113 - 16
tests/gui/server/http/test_file_upload.py

@@ -24,45 +24,46 @@ from taipy.gui.utils import _get_non_existent_file_path
 
 def test_file_upload_no_varname(gui: Gui, helpers):
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     with pytest.warns(UserWarning):
-        ret = flask_client.post(f"/taipy-uploads?client_id={sid}")
+        ret = server_client.post(f"/taipy-uploads?client_id={sid}")
         assert ret.status_code == 400
 
 
 def test_file_upload_no_blob(gui: Gui, helpers):
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     with pytest.warns(UserWarning):
-        ret = flask_client.post(f"/taipy-uploads?client_id={sid}", data={"var_name": "varname"})
+        ret = server_client.post(f"/taipy-uploads?client_id={sid}", data={"var_name": "varname"})
         assert ret.status_code == 400
 
 
 def test_file_upload_no_filename(gui: Gui, helpers):
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     file = (io.BytesIO(b"abcdef"), "")
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     with pytest.warns(UserWarning):
-        ret = flask_client.post(f"/taipy-uploads?client_id={sid}", data={"var_name": "varname", "blob": file})
+        ret = server_client.post(f"/taipy-uploads?client_id={sid}", data={"var_name": "varname", "blob": file})
         assert ret.status_code == 400
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_file_upload_simple(gui: Gui, helpers):
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     file_name = "test.jpg"
     file = (io.BytesIO(b"abcdef"), file_name)
     upload_path = pathlib.Path(gui._get_config("upload_folder", tempfile.gettempdir()))
     file_name = _get_non_existent_file_path(upload_path, file_name).name
-    ret = flask_client.post(
+    ret = server_client.post(
         f"/taipy-uploads?client_id={sid}",
         data={"var_name": "varname", "blob": file},
         content_type="multipart/form-data",
@@ -72,9 +73,29 @@ def test_file_upload_simple(gui: Gui, helpers):
     assert created_file.exists()
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+def test_file_upload_simple_fastapi(gui: Gui, helpers):
+    gui.run(run_server=False)
+    server_client = gui._server.test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    file_name = "test.jpg"
+    file = (io.BytesIO(b"abcdef"), file_name, "image/jpeg")
+    upload_path = pathlib.Path(gui._get_config("upload_folder", tempfile.gettempdir()))
+    file_name = _get_non_existent_file_path(upload_path, file_name).name
+    ret = server_client.post(
+        f"/taipy-uploads?client_id={sid}",
+        files={"blob": (file[1], file[0], file[2])},
+        data={"var_name": "varname"},
+    )
+    assert ret.status_code == 200
+    created_file = upload_path / file_name
+    assert created_file.exists()
+
+
+@pytest.mark.skip_if_not_server("flask")
 def test_file_upload_multi_part(gui: Gui, helpers):
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     file_name = "test2.jpg"
@@ -82,13 +103,13 @@ def test_file_upload_multi_part(gui: Gui, helpers):
     file1 = (io.BytesIO(b"abcdef"), file_name)
     upload_path = pathlib.Path(gui._get_config("upload_folder", tempfile.gettempdir()))
     file_name = _get_non_existent_file_path(upload_path, file_name).name
-    ret = flask_client.post(
+    ret = server_client.post(
         f"/taipy-uploads?client_id={sid}",
         data={"var_name": "varname", "blob": file0, "total": "2", "part": "0"},
         content_type="multipart/form-data",
     )
     assert ret.status_code == 200
-    ret = flask_client.post(
+    ret = server_client.post(
         f"/taipy-uploads?client_id={sid}",
         data={"var_name": "varname", "blob": file1, "total": "2", "part": "1"},
         content_type="multipart/form-data",
@@ -98,24 +119,51 @@ def test_file_upload_multi_part(gui: Gui, helpers):
     assert file_path.exists()
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+def test_file_upload_multi_part_fastapi(gui: Gui, helpers):
+    gui.run(run_server=False)
+    server_client = gui._server.test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    file_name = "test2.jpg"
+    file0 = (io.BytesIO(b"abcdef"), file_name, "image/jpeg")
+    file1 = (io.BytesIO(b"abcdef"), file_name, "image/jpeg")
+    upload_path = pathlib.Path(gui._get_config("upload_folder", tempfile.gettempdir()))
+    file_name = _get_non_existent_file_path(upload_path, file_name).name
+    ret = server_client.post(
+        f"/taipy-uploads?client_id={sid}",
+        files={"blob": (file0[1], file0[0], file0[2])},
+        data={"var_name": "varname", "total": "2", "part": "0"},
+    )
+    assert ret.status_code == 200
+    ret = server_client.post(
+        f"/taipy-uploads?client_id={sid}",
+        files={"blob": (file1[1], file1[0], file1[2])},
+        data={"var_name": "varname", "total": "2", "part": "1"},
+    )
+    assert ret.status_code == 200
+    file_path = upload_path / file_name
+    assert file_path.exists()
+
+
+@pytest.mark.skip_if_not_server("flask")
 def test_file_upload_multiple(gui: Gui, helpers):
     var_name = "varname"
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False, single_client=True)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     with gui.get_app_context():
         gui._bind_var_val(var_name, None)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = _DataScopes._GLOBAL_ID
     file = (io.BytesIO(b"abcdef"), "test.jpg")
-    ret = flask_client.post(
+    ret = server_client.post(
         f"/taipy-uploads?client_id={sid}", data={"var_name": var_name, "blob": file}, content_type="multipart/form-data"
     )
     assert ret.status_code == 200
     created_file = pathlib.Path(gui._get_config("upload_folder", tempfile.gettempdir())) / "test.jpg"
     assert created_file.exists()
     file2 = (io.BytesIO(b"abcdef"), "test2.jpg")
-    ret = flask_client.post(
+    ret = server_client.post(
         f"/taipy-uploads?client_id={sid}",
         data={"var_name": var_name, "blob": file2, "multiple": "True"},
         content_type="multipart/form-data",
@@ -127,20 +175,69 @@ def test_file_upload_multiple(gui: Gui, helpers):
     assert len(value) == 2
 
 
+@pytest.mark.skip_if_not_server("fastapi")
+def test_file_upload_multiple_fastapi(gui: Gui, helpers):
+    var_name = "varname"
+    gui._set_frame(inspect.currentframe())
+    gui.run(run_server=False, single_client=True)
+    server_client = gui._server.test_client()
+    with gui.get_app_context():
+        gui._bind_var_val(var_name, None)
+    sid = _DataScopes._GLOBAL_ID
+    file1 = (io.BytesIO(b"abcdef"), "test.jpg", "image/jpeg")
+    file2 = (io.BytesIO(b"abcdef"), "test2.jpg", "image/jpeg")
+    ret = server_client.post(
+        f"/taipy-uploads?client_id={sid}",
+        files={"blob": (file1[1], file1[0], file1[2])},
+        data={"var_name": var_name},
+    )
+    assert ret.status_code == 200
+    ret = server_client.post(
+        f"/taipy-uploads?client_id={sid}",
+        files={"blob": (file2[1], file2[0], file2[2])},
+        data={"var_name": var_name, "multiple": "True"},
+    )
+    assert ret.status_code == 200
+    value = getattr(gui._bindings()._get_all_scopes()[sid], var_name)
+    assert len(value) == 2
+
+
+@pytest.mark.skip_if_not_server("flask")
 def test_file_upload_folder(gui: Gui, helpers):
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False, single_client=True)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
 
     sid = _DataScopes._GLOBAL_ID
     files = [(io.BytesIO(b"(^~^)"), "cutey.txt"), (io.BytesIO(b"(^~^)"), "cute_nested.txt")]
     folders = [["folder"], ["folder", "nested"]]
     for file, folder in zip(files, folders):
         path = os.path.join(*folder, file[1])
-        response = flask_client.post(
+        response = server_client.post(
             f"/taipy-uploads?client_id={sid}",
             data={"var_name": "cute_varname", "blob": file, "path": path},
             content_type="multipart/form-data",
         )
         assert response.status_code == 200
         assert os.path.isfile(os.path.join(gui._get_config("upload_folder", tempfile.gettempdir()), path))
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+def test_file_upload_folder_fastapi(gui: Gui, helpers):
+    gui._set_frame(inspect.currentframe())
+    gui.run(run_server=False, single_client=True)
+    server_client = gui._server.test_client()
+
+    sid = _DataScopes._GLOBAL_ID
+    files = [(io.BytesIO(b"(^~^)"), "cutey.txt", "text/plain"), (io.BytesIO(b"(^~^)"), "cute_nested.txt", "text/plain")]
+    folders = [["folder"], ["folder", "nested"]]
+    for file, folder in zip(files, folders):
+        path = os.path.join(*folder, file[1])
+        response = server_client.post(
+            f"/taipy-uploads?client_id={sid}",
+            files={"blob": (file[1], file[0], file[2])},
+            data={"var_name": "cute_varname", "path": path},
+        )
+        assert response.status_code == 200
+        expected_file_path = os.path.join(gui._get_config("upload_folder", tempfile.gettempdir()), path)
+        assert os.path.isfile(expected_file_path)

+ 4 - 4
tests/gui/server/http/test_image_path.py

@@ -16,10 +16,10 @@ from taipy.gui import Gui
 
 def test_image_path_not_found(gui: Gui, helpers):
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
-    ret = flask_client.get(f"/taipy-images/images/img.png?client_id={sid}")
+    ret = server_client.get(f"/taipy-images/images/img.png?client_id={sid}")
     assert ret.status_code == 404
 
 
@@ -28,10 +28,10 @@ def test_image_path_found(gui: Gui, helpers):
         "img", str((pathlib.Path(__file__).parent.parent.parent / "resources" / "fred.png").resolve()), True
     )
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
-    ret = flask_client.get(f"{url}?client_id={sid}")
+    ret = server_client.get(f"{url}?client_id={sid}")
     assert ret.status_code == 200
 
 

+ 30 - 19
tests/gui/server/http/test_status.py

@@ -15,28 +15,38 @@ from os import path
 from taipy.gui import Gui
 
 
-def test_get_status(gui: Gui):
+def test_get_status(gui: Gui, helpers, gui_server):
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
-    ret = flask_client.get("/taipy.status.json")
+    server_client = gui._server.test_client()
+    ret = server_client.get("/taipy.status.json")
     assert ret.status_code == 200, f"status_code => {ret.status_code} != 200"
-    assert ret.mimetype == "application/json", f"mimetype => {ret.mimetype} != application/json"
-    assert ret.json, "json is not defined"
-    assert "gui" in ret.json, "json has no key gui"
-    gui = ret.json.get("gui")
+    if gui_server == "flask":
+        assert ret.mimetype == "application/json", f"mimetype => {ret.mimetype} != application/json"
+    elif gui_server == "fastapi":
+        mimetype = ret.headers["content-type"]
+        assert mimetype == "application/json", f"mimetype => {mimetype} != application/json"
+    ret_json = helpers.get_response_data(ret)
+    assert ret_json, "json is not defined"
+    assert "gui" in ret_json, "json has no key gui"
+    gui = ret_json.get("gui")
     assert isinstance(gui, dict), "json.gui is not a dict"
     assert "user_status" in gui, "json.gui has no key user_status"
     assert gui.get("user_status") == "", "json.gui.user_status is not empty"
 
 
-def test_get_extended_status(gui: Gui):
+def test_get_extended_status(gui: Gui, helpers, gui_server):
     gui.run(run_server=False, extended_status=True)
-    flask_client = gui._server.test_client()
-    ret = flask_client.get("/taipy.status.json")
+    server_client = gui._server.test_client()
+    ret = server_client.get("/taipy.status.json")
     assert ret.status_code == 200, f"status_code => {ret.status_code} != 200"
-    assert ret.mimetype == "application/json", f"mimetype => {ret.mimetype} != application/json"
-    assert ret.json, "json is not defined"
-    gui_json = ret.json.get("gui")
+    if gui_server == "flask":
+        assert ret.mimetype == "application/json", f"mimetype => {ret.mimetype} != application/json"
+    elif gui_server == "fastapi":
+        mimetype = ret.headers["content-type"]
+        assert mimetype == "application/json", f"mimetype => {mimetype} != application/json"
+    ret_json = helpers.get_response_data(ret)
+    assert ret_json, "json is not defined"
+    gui_json = ret_json.get("gui")
     assert "backend_version" in gui_json, "json.gui has no key backend_version"
     assert "flask_version" in gui_json, "json.gui has no key flask_version"
     if path.exists(gui._get_webapp_path()):
@@ -47,7 +57,7 @@ def test_get_extended_status(gui: Gui):
     assert gui_json.get("user_status") == "", "json.gui.user_status is not empty"
 
 
-def test_get_status_with_user_status(gui: Gui):
+def test_get_status_with_user_status(gui: Gui, helpers):
     user_status = "user_status"
 
     def on_status(state):
@@ -56,11 +66,12 @@ def test_get_status_with_user_status(gui: Gui):
     gui._set_frame(inspect.currentframe())
 
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
-    ret = flask_client.get("/taipy.status.json")
+    server_client = gui._server.test_client()
+    ret = server_client.get("/taipy.status.json")
+    ret_json = helpers.get_response_data(ret)
     assert ret.status_code == 200, f"status_code => {ret.status_code} != 200"
-    assert ret.json, "json is not defined"
-    gui_ret = ret.json.get("gui")
+    assert ret_json, "json is not defined"
+    gui_ret = ret_json.get("gui")
     assert "user_status" in gui_ret, "json.gui has no key user_status"
     assert gui_ret.get("user_status") == user_status
-    assert f'json.gui.user_status => {gui_ret.get("user_status")} != {user_status}'
+    assert f"json.gui.user_status => {gui_ret.get('user_status')} != {user_status}"

+ 6 - 6
tests/gui/server/http/test_user_content.py

@@ -18,9 +18,9 @@ from taipy.gui import Gui
 
 def test_user_content_without_callback(gui: Gui, helpers):
     gui.run(run_server=False, single_client=True)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     with pytest.warns(UserWarning):
-        ret = flask_client.get(gui._get_user_content_url("path"))
+        ret = server_client.get(gui._get_user_content_url("path"))
         assert ret.status_code == 404
 
 
@@ -31,9 +31,9 @@ def test_user_content_with_wrong_callback(gui: Gui, helpers):
     on_user_content = on_user_content_cb  # noqa: F841
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False, single_client=True)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     with pytest.warns(UserWarning):
-        ret = flask_client.get(gui._get_user_content_url("path", {"a": "b"}))
+        ret = server_client.get(gui._get_user_content_url("path", {"a": "b"}))
         assert ret.status_code == 404
 
 
@@ -44,6 +44,6 @@ def test_user_content_with_callback(gui: Gui, helpers):
     on_user_content = on_user_content_cb  # noqa: F841
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False, single_client=True)
-    flask_client = gui._server.test_client()
-    ret = flask_client.get(gui._get_user_content_url("path"))
+    server_client = gui._server.test_client()
+    ret = server_client.get(gui._get_user_content_url("path"))
     assert ret.status_code == 200

+ 40 - 2
tests/gui/server/ws/test_a.py

@@ -11,9 +11,13 @@
 
 import inspect
 
+import pytest
+
 from taipy.gui import Gui, Markdown
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_a_button_pressed(gui: Gui, helpers):
     def do_something(state, id):
         state.x = state.x + 10
@@ -28,12 +32,12 @@ def test_a_button_pressed(gui: Gui, helpers):
         "test", Markdown("<|Do something!|button|on_action=do_something|id=my_button|> | <|{x}|> | <|{text}|>")
     )
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={sid}")
+    server_client.get(f"/taipy-jsx/test?client_id={sid}")
     assert gui._bindings()._get_all_scopes()[sid].x == 10  # type: ignore
     assert gui._bindings()._get_all_scopes()[sid].text == "hi"  # type: ignore
     ws_client.emit("message", {"client_id": sid, "type": "A", "name": "my_button", "payload": "do_something"})
@@ -43,3 +47,37 @@ def test_a_button_pressed(gui: Gui, helpers):
     received_messages = ws_client.get_received()
     helpers.assert_outward_ws_message(received_messages[0], "MU", "tpec_TpExPr_x_TPMDL_0", 20)
     helpers.assert_outward_ws_message(received_messages[1], "MU", "tpec_TpExPr_text_TPMDL_0", "a random text")
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_a_button_pressed_fastapi(gui: Gui, helpers):
+    def do_something(state, id):
+        state.x = state.x + 10
+        state.text = "a random text"
+
+    x = 10  # noqa: F841
+    text = "hi"  # noqa: F841
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+    # Bind a page so that the variable will be evaluated as expression
+    gui.add_page(
+        "test", Markdown("<|Do something!|button|on_action=do_something|id=my_button|> | <|{x}|> | <|{text}|>")
+    )
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    RequestAccessor.set_sid(ws_client.get_sid())
+    ws_client.get(f"/taipy-jsx/test?client_id={sid}")
+    assert gui._bindings()._get_all_scopes()[sid].x == 10  # type: ignore
+    assert gui._bindings()._get_all_scopes()[sid].text == "hi"  # type: ignore
+    ws_client.emit("message", {"client_id": sid, "type": "A", "name": "my_button", "payload": "do_something"})
+    assert gui._bindings()._get_all_scopes()[sid].text == "a random text"
+    assert gui._bindings()._get_all_scopes()[sid].x == 20  # type: ignore
+    # assert for received message (message that would be sent to the front-end client)
+    received_messages = ws_client.get_received()
+    try:
+        helpers.assert_outward_ws_message(received_messages[0], "MU", "tpec_TpExPr_x_TPMDL_0", 20)
+        helpers.assert_outward_ws_message(received_messages[1], "MU", "tpec_TpExPr_text_TPMDL_0", "a random text")
+    finally:
+        ws_client.disconnect()

+ 34 - 2
tests/gui/server/ws/test_broadcast.py

@@ -11,9 +11,13 @@
 
 import inspect
 
+import pytest
+
 from taipy.gui import Gui, Markdown
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_broadcast(gui: Gui, helpers):
     # Bind test variables
     selected_val = ["value1", "value2"]  # noqa: F841
@@ -27,13 +31,41 @@ def test_broadcast(gui: Gui, helpers):
         Markdown("<|{selected_val}|selector|multiple|>"),
     )
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     sid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={sid}")
+    server_client.get(f"/taipy-jsx/test?client_id={sid}")
     gui._broadcast("broadcast_name", "broadcast_value")
     received_messages = ws_client.get_received()
     assert len(received_messages)
     helpers.assert_outward_simple_ws_message(received_messages[0], "BC", "_bc_broadcast_name", "broadcast_value")
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_broadcast_fastapi(gui: Gui, helpers):
+    # Bind test variables
+    selected_val = ["value1", "value2"]  # noqa: F841
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    # Bind a page so that the variable will be evaluated as expression
+    gui.add_page(
+        "test",
+        Markdown("<|{selected_val}|selector|multiple|>"),
+    )
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    RequestAccessor.set_sid(ws_client.get_sid())
+    ws_client.get(f"/taipy-jsx/test?client_id={sid}")
+    gui._broadcast("broadcast_name", "broadcast_value")
+    received_messages = ws_client.get_received()
+    try:
+        assert len(received_messages)
+        helpers.assert_outward_simple_ws_message(received_messages[0], "BC", "_bc_broadcast_name", "broadcast_value")
+    finally:
+        ws_client.disconnect()

+ 36 - 0
tests/gui/server/ws/test_df.py

@@ -13,9 +13,13 @@ import inspect
 import logging
 import pathlib
 
+import pytest
+
 from taipy.gui import Gui, download
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_download_file(gui: Gui, helpers):
     def do_something(state, id):
         download(state, (pathlib.Path(__file__).parent.parent.parent / "resources" / "taipan.jpg"))
@@ -43,3 +47,35 @@ def test_download_file(gui: Gui, helpers):
     assert "type" in args and args["type"] == "DF"
     assert "content" in args and args["content"] == "/taipy-content/taipyStatic0/taipan.jpg"
     logging.getLogger().debug(args["content"])
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_download_file_fastapi(gui: Gui, helpers):
+    def do_something(state, id):
+        download(state, (pathlib.Path(__file__).parent.parent.parent / "resources" / "taipan.jpg"))
+
+    # Bind a page so that the function will be called
+    # gui.add_page(
+    #     "test", Markdown("<|Do something!|button|on_action=do_something|id=my_button|>")
+    # )
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    RequestAccessor.set_sid(ws_client.get_sid())
+    ws_client.emit("message", {"client_id": sid, "type": "A", "name": "my_button", "payload": "do_something"})
+    # assert for received message (message that would be sent to the front-end client)
+    received_messages = ws_client.get_received()
+    try:
+        assert len(received_messages) == 1
+        assert isinstance(received_messages[0], dict)
+        assert "name" in received_messages[0] and received_messages[0]["name"] == "message"
+        assert "args" in received_messages[0]
+        args = received_messages[0]["args"]
+        assert "type" in args and args["type"] == "DF"
+        assert "content" in args and args["content"] == "/taipy-content/taipyStatic0/taipan.jpg"
+        logging.getLogger().debug(args["content"])
+    finally:
+        ws_client.disconnect()

+ 133 - 81
tests/gui/server/ws/test_du.py

@@ -11,9 +11,95 @@
 
 import inspect
 
+import pytest
+
 from taipy.gui import Gui, Markdown
+from taipy.gui.servers.request import RequestAccessor
+
+asserted_content = (
+    "MU",
+    "_TpD_tpec_TpExPr_csvdata_TPMDL_0",
+    {
+        "data": [
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-01T00:00:00.000000Z",
+                "Daily hospital occupancy": 856,
+                "Entity": "Austria",
+                "_tp_index": 0,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-02T00:00:00.000000Z",
+                "Daily hospital occupancy": 823,
+                "Entity": "Austria",
+                "_tp_index": 1,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-03T00:00:00.000000Z",
+                "Daily hospital occupancy": 829,
+                "Entity": "Austria",
+                "_tp_index": 2,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-04T00:00:00.000000Z",
+                "Daily hospital occupancy": 826,
+                "Entity": "Austria",
+                "_tp_index": 3,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-05T00:00:00.000000Z",
+                "Daily hospital occupancy": 712,
+                "Entity": "Austria",
+                "_tp_index": 4,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-06T00:00:00.000000Z",
+                "Daily hospital occupancy": 824,
+                "Entity": "Austria",
+                "_tp_index": 5,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-07T00:00:00.000000Z",
+                "Daily hospital occupancy": 857,
+                "Entity": "Austria",
+                "_tp_index": 6,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-08T00:00:00.000000Z",
+                "Daily hospital occupancy": 829,
+                "Entity": "Austria",
+                "_tp_index": 7,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-09T00:00:00.000000Z",
+                "Daily hospital occupancy": 820,
+                "Entity": "Austria",
+                "_tp_index": 8,
+            },
+            {
+                "Code": "AUT",
+                "Day_str": "2020-04-10T00:00:00.000000Z",
+                "Daily hospital occupancy": 771,
+                "Entity": "Austria",
+                "_tp_index": 9,
+            },
+        ],
+        "rowcount": 14477,
+        "start": 0,
+        "format": "JSON",
+    },
+)
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_du_table_data_fetched(gui: Gui, helpers, csvdata):
     # Bind test variables
     csvdata = csvdata
@@ -30,12 +116,12 @@ def test_du_table_data_fetched(gui: Gui, helpers, csvdata):
         ),
     )
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     sid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={sid}")
+    server_client.get(f"/taipy-jsx/test?client_id={sid}")
     ws_client.emit(
         "message",
         {
@@ -55,85 +141,51 @@ def test_du_table_data_fetched(gui: Gui, helpers, csvdata):
     # assert for received message (message that would be sent to the front-end client)
     received_messages = ws_client.get_received()
     assert received_messages
-    helpers.assert_outward_ws_message(
-        received_messages[0],
-        "MU",
-        "_TpD_tpec_TpExPr_csvdata_TPMDL_0",
+    helpers.assert_outward_ws_message(received_messages[0], *asserted_content)
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_du_table_data_fetched_fastapi(gui: Gui, helpers, csvdata):
+    # Bind test variables
+    csvdata = csvdata
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+    Gui._set_timezone("UTC")
+
+    # Bind a page so that the variable will be evaluated as expression
+    gui.add_page(
+        "test",
+        Markdown(
+            "<|{csvdata}|table|page_size=10|page_size_options=10;30;100|columns=Day;Entity;Code;Daily hospital occupancy|date_format=eee dd MMM yyyy|>"  # noqa: E501
+        ),
+    )
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    RequestAccessor.set_sid(ws_client.get_sid())
+    ws_client.get(f"/taipy-jsx/test?client_id={sid}")
+    ws_client.emit(
+        "message",
         {
-            "data": [
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-01T00:00:00.000000Z",
-                    "Daily hospital occupancy": 856,
-                    "Entity": "Austria",
-                    "_tp_index": 0,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-02T00:00:00.000000Z",
-                    "Daily hospital occupancy": 823,
-                    "Entity": "Austria",
-                    "_tp_index": 1,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-03T00:00:00.000000Z",
-                    "Daily hospital occupancy": 829,
-                    "Entity": "Austria",
-                    "_tp_index": 2,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-04T00:00:00.000000Z",
-                    "Daily hospital occupancy": 826,
-                    "Entity": "Austria",
-                    "_tp_index": 3,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-05T00:00:00.000000Z",
-                    "Daily hospital occupancy": 712,
-                    "Entity": "Austria",
-                    "_tp_index": 4,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-06T00:00:00.000000Z",
-                    "Daily hospital occupancy": 824,
-                    "Entity": "Austria",
-                    "_tp_index": 5,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-07T00:00:00.000000Z",
-                    "Daily hospital occupancy": 857,
-                    "Entity": "Austria",
-                    "_tp_index": 6,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-08T00:00:00.000000Z",
-                    "Daily hospital occupancy": 829,
-                    "Entity": "Austria",
-                    "_tp_index": 7,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-09T00:00:00.000000Z",
-                    "Daily hospital occupancy": 820,
-                    "Entity": "Austria",
-                    "_tp_index": 8,
-                },
-                {
-                    "Code": "AUT",
-                    "Day_str": "2020-04-10T00:00:00.000000Z",
-                    "Daily hospital occupancy": 771,
-                    "Entity": "Austria",
-                    "_tp_index": 9,
-                },
-            ],
-            "rowcount": 14477,
-            "start": 0,
-            "format": "JSON",
+            "client_id": sid,
+            "type": "DU",
+            "name": "_TpD_tpec_TpExPr_csvdata_TPMDL_0",
+            "payload": {
+                "columns": ["Day", "Entity", "Code", "Daily hospital occupancy"],
+                "pagekey": "0-100-asc",
+                "start": 0,
+                "end": 9,
+                "orderby": "",
+                "sort": "asc",
+            },
         },
     )
+    # assert for received message (message that would be sent to the front-end client)
+    received_messages = ws_client.get_received()
+    try:
+        assert received_messages
+        helpers.assert_outward_ws_message(received_messages[0], *asserted_content)
+    finally:
+        ws_client.disconnect()

+ 71 - 4
tests/gui/server/ws/test_on_change.py

@@ -11,9 +11,13 @@
 
 import inspect
 
+import pytest
+
 from taipy.gui import Gui, Markdown
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_default_on_change(gui: Gui, helpers):
     st = {"d": False}
 
@@ -27,18 +31,19 @@ def test_default_on_change(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|{x}|input|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={sid}")
+    server_client.get(f"/taipy-jsx/test?client_id={sid}")
     # fake var update
     ws_client.emit("message", {"client_id": sid, "type": "U", "name": "x", "payload": {"value": "20"}})
     assert ws_client.get_received()
     assert st["d"] is True
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_specific_on_change(gui: Gui, helpers):
     st = {"d": False, "s": False}
 
@@ -55,12 +60,12 @@ def test_specific_on_change(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|{x}|input|on_change=on_input_change|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={sid}")
+    server_client.get(f"/taipy-jsx/test?client_id={sid}")
     # fake var update
     ws_client.emit(
         "message",
@@ -69,3 +74,65 @@ def test_specific_on_change(gui: Gui, helpers):
     assert ws_client.get_received()
     assert st["s"] is True
     assert st["d"] is False
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_default_on_change_fastapi(gui: Gui, helpers):
+    st = {"d": False}
+
+    def on_change(state, var, value):
+        st["d"] = True
+
+    x = 10  # noqa: F841
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|{x}|input|>"))
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    RequestAccessor.set_sid(ws_client.get_sid())
+    ws_client.get(f"/taipy-jsx/test?client_id={sid}")
+    # fake var update
+    ws_client.emit("message", {"client_id": sid, "type": "U", "name": "x", "payload": {"value": "20"}})
+    try:
+        assert ws_client.get_received()
+        assert st["d"] is True
+    finally:
+        ws_client.disconnect()
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_specific_on_change_fastapi(gui: Gui, helpers):
+    st = {"d": False, "s": False}
+
+    def on_change(state, var, value):
+        st["d"] = True
+
+    def on_input_change(state, var, value):
+        st["s"] = True
+
+    x = 10  # noqa: F841
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|{x}|input|on_change=on_input_change|>"))
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    RequestAccessor.set_sid(ws_client.get_sid())
+    ws_client.get(f"/taipy-jsx/test?client_id={sid}")
+    ws_client.emit(
+        "message",
+        {"client_id": sid, "type": "U", "name": "x", "payload": {"value": "20", "on_change": "on_input_change"}},
+    )
+    try:
+        assert ws_client.get_received()
+        assert st["s"] is True
+        assert st["d"] is False
+    finally:
+        ws_client.disconnect()

+ 40 - 2
tests/gui/server/ws/test_ru.py

@@ -11,9 +11,13 @@
 
 import inspect
 
+import pytest
+
 from taipy.gui import Gui, Markdown
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_ru_selector(gui: Gui, helpers, csvdata):
     # Bind test variables
     selected_val = ["value1", "value2"]  # noqa: F841
@@ -27,12 +31,12 @@ def test_ru_selector(gui: Gui, helpers, csvdata):
         Markdown("<|{selected_val}|selector|multiple|>"),
     )
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     sid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={sid}")
+    server_client.get(f"/taipy-jsx/test?client_id={sid}")
     ws_client.emit(
         "message",
         {"client_id": sid, "type": "RU", "name": "", "payload": {"names": ["_TpLv_tpec_TpExPr_selected_val_TPMDL_0"]}},
@@ -43,3 +47,37 @@ def test_ru_selector(gui: Gui, helpers, csvdata):
     helpers.assert_outward_ws_message(
         received_messages[0], "MU", "_TpLv_tpec_TpExPr_selected_val_TPMDL_0", ["value1", "value2"]
     )
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_ru_selector_fastapi(gui: Gui, helpers, csvdata):
+    # Bind test variables
+    selected_val = ["value1", "value2"]  # noqa: F841
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    # Bind a page so that the variable will be evaluated as expression
+    gui.add_page(
+        "test",
+        Markdown("<|{selected_val}|selector|multiple|>"),
+    )
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    RequestAccessor.set_sid(ws_client.get_sid())
+    ws_client.get(f"/taipy-jsx/test?client_id={sid}")
+    ws_client.emit(
+        "message",
+        {"client_id": sid, "type": "RU", "name": "", "payload": {"names": ["_TpLv_tpec_TpExPr_selected_val_TPMDL_0"]}},
+    )
+    # assert for received message (message that would be sent to the front-end client)
+    received_messages = ws_client.get_received()
+    try:
+        assert len(received_messages)
+        helpers.assert_outward_ws_message(
+            received_messages[0], "MU", "_TpLv_tpec_TpExPr_selected_val_TPMDL_0", ["value1", "value2"]
+        )
+    finally:
+        ws_client.disconnect()

+ 41 - 6
tests/gui/server/ws/test_u.py

@@ -12,6 +12,7 @@
 import inspect
 
 from taipy.gui import Gui, Markdown
+from taipy.gui.servers.request import RequestAccessor
 
 
 def ws_u_assert_template(gui: Gui, helpers, value_before_update, value_after_update, payload):
@@ -24,12 +25,12 @@ def ws_u_assert_template(gui: Gui, helpers, value_before_update, value_after_upd
     # Bind a page so that the variable will be evaluated as expression
     gui.add_page("test", Markdown("<|{var}|>"))
     gui.run(run_server=False)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
-    flask_client.get(f"/taipy-jsx/test?client_id={sid}")
+    server_client.get(f"/taipy-jsx/test?client_id={sid}")
     assert gui._bindings()._get_all_scopes()[sid].var == value_before_update
     ws_client.emit("message", {"client_id": sid, "type": "U", "name": "tpec_TpExPr_var_TPMDL_0", "payload": payload})
     assert gui._bindings()._get_all_scopes()[sid].var == value_after_update
@@ -39,7 +40,35 @@ def ws_u_assert_template(gui: Gui, helpers, value_before_update, value_after_upd
     helpers.assert_outward_ws_message(received_message[0], "MU", "tpec_TpExPr_var_TPMDL_0", value_after_update)
 
 
-def test_ws_u_string(gui: Gui, helpers):
+def ws_u_assert_template_fastapi(gui: Gui, helpers, value_before_update, value_after_update, payload):
+    # Bind test variable
+    var = value_before_update  # noqa: F841
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    # Bind a page so that the variable will be evaluated as expression
+    gui.add_page("test", Markdown("<|{var}|>"))
+    helpers.run_e2e_multi_client(gui)
+    ws_client = helpers.get_socketio_test_client()
+    sid = helpers.create_scope_and_get_sid(gui)
+    RequestAccessor.set_sid(ws_client.get_sid())
+    ws_client.get(f"/taipy-jsx/test?client_id={sid}")
+    try:
+        assert gui._bindings()._get_all_scopes()[sid].var == value_before_update
+        ws_client.emit(
+            "message", {"client_id": sid, "type": "U", "name": "tpec_TpExPr_var_TPMDL_0", "payload": payload}
+        )
+        assert gui._bindings()._get_all_scopes()[sid].var == value_after_update
+        # assert for received message (message that would be sent to the front-end client)
+        received_message = ws_client.get_received()
+        assert len(received_message)
+        helpers.assert_outward_ws_message(received_message[0], "MU", "tpec_TpExPr_var_TPMDL_0", value_after_update)
+    finally:
+        ws_client.disconnect()
+
+
+def test_ws_u_string(gui: Gui, helpers, gui_server):
     value_before_update = "a random string"
     value_after_update = "a random string is added"
     payload = {"value": value_after_update}
@@ -47,10 +76,13 @@ def test_ws_u_string(gui: Gui, helpers):
     # set gui frame
     gui._set_frame(inspect.currentframe())
 
-    ws_u_assert_template(gui, helpers, value_before_update, value_after_update, payload)
+    if gui_server == "flask":
+        ws_u_assert_template(gui, helpers, value_before_update, value_after_update, payload)
+    elif gui_server == "fastapi":
+        ws_u_assert_template_fastapi(gui, helpers, value_before_update, value_after_update, payload)
 
 
-def test_ws_u_number(gui: Gui, helpers):
+def test_ws_u_number(gui: Gui, helpers, gui_server):
     value_before_update = 10
     value_after_update = "11"
     payload = {"value": value_after_update}
@@ -58,4 +90,7 @@ def test_ws_u_number(gui: Gui, helpers):
     # set gui frame
     gui._set_frame(inspect.currentframe())
 
-    ws_u_assert_template(gui, helpers, value_before_update, value_after_update, payload)
+    if gui_server == "flask":
+        ws_u_assert_template(gui, helpers, value_before_update, value_after_update, payload)
+    elif gui_server == "fastapi":
+        ws_u_assert_template_fastapi(gui, helpers, value_before_update, value_after_update, payload)

+ 41 - 4
tests/gui/server/ws/test_with.py

@@ -11,10 +11,14 @@
 
 import inspect
 
+import pytest
+
 from taipy.gui import Gui, Markdown
 from taipy.gui.data.data_scope import _DataScopes
+from taipy.gui.servers.request import RequestAccessor
 
 
+@pytest.mark.skip_if_not_server("flask")
 def test_sending_messages_in_group(gui: Gui, helpers):
     name = "World!"  # noqa: F841
     btn_id = "button1"  # noqa: F841
@@ -24,22 +28,55 @@ def test_sending_messages_in_group(gui: Gui, helpers):
 
     gui.add_page("test", Markdown("<|Hello {name}|button|id={btn_id}|>"))
     gui.run(run_server=False, single_client=True)
-    flask_client = gui._server.test_client()
+    server_client = gui._server.test_client()
     # WS client and emit
     ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     cid = _DataScopes._GLOBAL_ID
     # Get the jsx once so that the page will be evaluated -> variable will be registered
-    flask_client.get(f"/taipy-jsx/test?client_id={cid}")
+    server_client.get(f"/taipy-jsx/test?client_id={cid}")
     assert gui._bindings()._get_all_scopes()[cid].name == "World!"  # type: ignore
     assert gui._bindings()._get_all_scopes()[cid].btn_id == "button1"  # type: ignore
 
-    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         with gui as aGui:
             aGui._Gui__state.name = "Monde!"
             aGui._Gui__state.btn_id = "button2"
 
-    assert gui._bindings()._get_all_scopes()[cid].name == "Monde!"
     assert gui._bindings()._get_all_scopes()[cid].btn_id == "button2"  # type: ignore
+    assert gui._bindings()._get_all_scopes()[cid].name == "Monde!"
 
     received_messages = ws_client.get_received()
     helpers.assert_outward_ws_multiple_message(received_messages[0], "MS", 2)
+
+
+@pytest.mark.skip_if_not_server("fastapi")
+@pytest.mark.teste2e
+def test_sending_messages_in_group_fastapi(gui: Gui, helpers):
+    name = "World!"  # noqa: F841
+    btn_id = "button1"  # noqa: F841
+
+    # set gui frame
+    gui._set_frame(inspect.currentframe())
+
+    gui.add_page("test", Markdown("<|Hello {name}|button|id={btn_id}|>"))
+    helpers.run_e2e(gui)
+    ws_client = helpers.get_socketio_test_client()
+    RequestAccessor.set_sid(ws_client.get_sid())
+    cid = _DataScopes._GLOBAL_ID
+    ws_client.get(f"/taipy-jsx/test?client_id={cid}")
+    try:
+        assert gui._bindings()._get_all_scopes()[cid].name == "World!"  # type: ignore
+        assert gui._bindings()._get_all_scopes()[cid].btn_id == "button1"  # type: ignore
+
+        with gui._server.test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+            with gui as aGui:
+                aGui._Gui__state.name = "Monde!"
+                aGui._Gui__state.btn_id = "button2"
+
+        assert gui._bindings()._get_all_scopes()[cid].btn_id == "button2"  # type: ignore
+        assert gui._bindings()._get_all_scopes()[cid].name == "Monde!"
+
+        received_messages = ws_client.get_received()
+        helpers.assert_outward_ws_multiple_message(received_messages[0], "MS", 2)
+    finally:
+        ws_client.disconnect()