Repository: parkouss/funq (language: python)

Issue:
Fix the tutorial to be able to do it with Qt5
For now, the tutorial says that funq-server does not works with Qt5, which is wrong!

Maybe the provided player_tester sample application is not compatible though - so we need to make this compatible if needed, and fix the documentation: http://funq.readthedocs.org/en/latest/tutorial.html



Discussion / hints:
Fixed with https://github.com/parkouss/funq/commit/407ff9c7fd52f7c37c265f97e2eeb0fc7e55c6da.


Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..5c4de5c
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,225 @@
+name: CI
+on: [push, pull_request]
+
+env:
+  MAKEFLAGS: "-j8"
+
+jobs:
+
+  linux:
+    name: "qt:${{ matrix.qt }} py:${{ matrix.py }} on ${{ matrix.container }}"
+    runs-on: ubuntu-22.04
+    container: "${{ matrix.container }}"
+    strategy:
+      matrix:
+        include:
+          - qt: 5  # 5.12
+            py: "3.8"
+            container: "ubuntu:20.04"
+            packages: "qt5-default qttools5-dev-tools qtdeclarative5-dev qml-module-qtquick2"
+            nosetests: 1
+          - qt: 5  # 5.15
+            py: "3.10"
+            container: "ubuntu:22.04"
+            packages: "qtbase5-dev qttools5-dev-tools qtdeclarative5-dev qml-module-qtquick2"
+            nosetests: 0  # Nosetest not working anymore
+          - qt: 6  # 6.2
+            py: "3.10"
+            container: "ubuntu:22.04"
+            packages: "qt6-base-dev qt6-tools-dev qt6-tools-dev-tools qt6-declarative-dev libqt6opengl6-dev qml6-module-*"
+            nosetests: 0  # Nosetest not working anymore
+          - qt: 6  # 6.4
+            py: "3.12"
+            container: "ubuntu:24.04"
+            packages: "qt6-base-dev qt6-tools-dev qt6-tools-dev-tools qt6-declarative-dev libqt6opengl6-dev qml6-module-*"
+            nosetests: 0  # Nosetest not working anymore
+    env:
+      DEBIAN_FRONTEND: noninteractive
+      FUNQ_QT_MAJOR_VERSION: "${{ matrix.qt }}"
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install requirements
+        run: |
+          apt-get update
+          apt-get install -y --no-install-recommends \
+            build-essential cmake xvfb libglu1-mesa-dev \
+            python3 python3-pip python3-flake8 \
+            ${{ matrix.packages }}
+
+      # Stylecheck
+      - name: Flake8
+        run: python3 -m flake8 client/funq server/funq_server
+
+      # Build & test C++ modules
+      - name: Build libFunq
+        run: |
+          mkdir build
+          cd build
+          cmake ../server -DQT_MAJOR_VERSION=${{ matrix.qt }} -DBUILD_TESTS=1 -DBUILD_DISALLOW_WARNINGS=1
+          make
+      - name: Run libFunq tests
+        run: xvfb-run -a build/tests/libFunq/testLibFunq
+      - name: Run protocole tests
+        run: xvfb-run -a build/tests/protocole/testProtocole
+
+      # Server
+      - name: Install server
+        run: cd server && python3 setup.py develop
+
+      # Client
+      - name: Install client
+        run: cd client && python3 setup.py develop
+      - name: Test client
+        run: cd client && python3 setup.py test
+        if: ${{ matrix.nosetests != 0 }}
+
+      # Functional tests
+      - name: Build test app
+        run: cd tests-functionnal/funq-test-app && cmake . && make
+      - name: Test injection
+        run: xvfb-run -a funq tests-functionnal/funq-test-app/funq-test-app --exit-after-startup
+      - name: Test functional
+        run: cd tests-functionnal && xvfb-run -a nosetests
+        if: ${{ matrix.nosetests != 0}}
+
+  macos:
+    name: "qt:${{ matrix.qt }} on ${{ matrix.runner }}"
+    runs-on: "${{ matrix.runner }}"
+    strategy:
+      matrix:
+        include:
+          # Note: Nosetest doesn't work anymore with recent Python versions!
+          - {qt: "5", runner: "macos-12", nosetests: 0}
+          - {qt: "6", runner: "macos-14", nosetests: 0}
+    env:
+      FUNQ_QT_MAJOR_VERSION: "${{ matrix.qt }}"
+      PIP_BREAK_SYSTEM_PACKAGES: 1
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install requirements
+        run: |
+          brew install cmake qt@${{ matrix.qt }}
+          brew link --force qt@${{ matrix.qt }}
+          echo "$(brew --prefix qt${{ matrix.qt }})/bin" >> $GITHUB_PATH
+          pip3 install setuptools flake8
+
+      # Stylecheck
+      - name: Flake8
+        run: python3 -m flake8 client/funq server/funq_server
+
+      # Build & test C++ modules
+      - name: Build libFunq
+        run: |
+          mkdir build
+          cd build
+          cmake ../server -DQT_MAJOR_VERSION=${{ matrix.qt }} -DBUILD_TESTS=1 -DBUILD_DISALLOW_WARNINGS=1
+          make
+      - name: Run libFunq tests
+        run: build/tests/libFunq/testLibFunq
+      - name: Run protocole tests
+        run: build/tests/protocole/testProtocole
+
+      # Server
+      - name: Install server
+        run: cd server && python3 setup.py develop
+
+      # Client
+      - name: Install client
+        run: cd client && python3 setup.py develop
+      - name: Test client
+        run: cd client && python3 setup.py test
+        if: ${{ matrix.nosetests != 0 }}
+
+      # Functional tests
+      - name: Build test app
+        run: cd tests-functionnal/funq-test-app && cmake . && make
+      - name: Test injection
+        run: funq tests-functionnal/funq-test-app/funq-test-app --exit-after-startup
+      - name: Test functional
+        run: cd tests-functionnal && xvfb-run -a nosetests
+        if: ${{ matrix.nosetests != 0}}
+
+  windows:
+    name: "qt:${{ matrix.qt }} py:${{ matrix.py }} ${{ matrix.arch }} on windows"
+    runs-on: windows-2022
+    strategy:
+      matrix:
+        include:
+          - py: "3.8"
+            arch: "x86"
+            qt: 5
+            qt_full: "5.15.2"
+            qt_arch: "win32_mingw81"
+            qt_tools: "tools_mingw,qt.tools.win32_mingw810"
+            compiler_path: "D:/a/funq/Qt/Tools/mingw810_32/bin"
+            nosetests: 1
+          - py: "3.8"
+            arch: "x64"
+            qt: 6
+            qt_full: "6.7.0"
+            qt_arch: "win64_mingw"
+            qt_tools: "tools_mingw1310"
+            compiler_path: "D:/a/funq/Qt/Tools/mingw1310_64/bin"
+            nosetests: 1
+          - py: "3.11"
+            arch: "x64"
+            qt: 6
+            qt_full: "6.7.0"
+            qt_arch: "win64_mingw"
+            qt_tools: "tools_mingw1310"
+            compiler_path: "D:/a/funq/Qt/Tools/mingw1310_64/bin"
+            nosetests: 0  # Nosetest not working anymore
+    env:
+      CMAKE_GENERATOR: "MinGW Makefiles"
+      CC: "${{ matrix.compiler_path }}/gcc.exe"
+      CXX: "${{ matrix.compiler_path }}/g++.exe"
+    defaults:
+      run:
+        shell: cmd
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: "${{ matrix.py }}"
+          architecture: "${{ matrix.arch }}"
+      - name: Install Qt
+        uses: jurplel/install-qt-action@v3
+        with:
+          version: "${{ matrix.qt_full }}"
+          tools: "${{ matrix.qt_tools }}"
+          arch: "${{ matrix.qt_arch }}"
+          setup-python: false
+          cache: true
+
+      # Build & test C++ modules
+      - name: Build libFunq
+        run: |
+          mkdir build
+          cd build
+          cmake ../server -DBUILD_TESTS=1 -DBUILD_DISALLOW_WARNINGS=1
+          make
+      - name: Run libFunq tests
+        run: build\tests\libFunq\testLibFunq.exe
+      - name: Run protocole tests
+        run: build\tests\protocole\testProtocole.exe
+
+      # Server
+      - name: Install server
+        run: cd server && python3 setup.py develop
+
+      # Client
+      - name: Install client
+        run: cd client && python3 setup.py develop
+      - name: Test client
+        run: cd client && python3 setup.py test
+        if: ${{ matrix.nosetests != 0 }}
+
+      # Functional tests
+      - name: Build test app
+        run: cd tests-functionnal/funq-test-app && cmake . && make
+      - name: Test injection
+        run: funq tests-functionnal/funq-test-app/funq-test-app.exe --exit-after-startup
+      - name: Test functional
+        run: cd tests-functionnal && nosetests
+        if: ${{ matrix.nosetests != 0}}
diff --git a/.gitignore b/.gitignore
index 4e8d178..06bc6b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,8 @@
 *.pyc
 *.egg-info
-_build
 build
 dist
 
-
 Makefile
 moc_*.*
 ui_*.h
@@ -18,14 +16,17 @@ ui_*.h
 *.pro.user
 *.Debug
 *.Release
-.qmake.stash
+cmake_install.cmake
+CMakeLists.txt.user
+CMakeCache.txt
 
+*_autogen/
+CMakeFiles/
 server/bin
-server/player_tester/Makefile
 server/player_tester/player_tester
 server/player_tester/tutorial
-server/tests/libFunq
-server/tests/protocole
+server/tests/libFunq/testLibFunq
+server/tests/protocole/testProtocole
 tests-functionnal/funq-test-app/funq-test-app
 tests-functionnal/screenshot-errors/
 tests-functionnal/screenshots/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 5adf5a2..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,149 +0,0 @@
-sudo: required
-cache:
-  - pip
-  - ccache
-notifications:
-  email:
-    on_success: change
-    on_failure: change
-
-services:
-  - xvfb
-
-env:
-  global:
-    - FUNQ_CXXFLAGS="-Wall -Wextra -Werror"  # make CI fail on warnings
-
-matrix:
-  fast_finish: true
-  include:
-
-    # MacOS Qt5 Python2
-    - os: osx
-      language: cpp
-      env: QT_SELECT=5 PYTHON=python2
-
-    # MacOS Qt5 Python3
-    - os: osx
-      language: cpp
-      env: QT_SELECT=5 PYTHON=python3
-
-    # Linux Qt4 Python2.7
-    - os: linux
-      dist: trusty
-      language: python
-      python: 2.7
-      env: QT_SELECT=4 PYTHON=python
-
-    # Linux Qt4 Python3.4
-    - os: linux
-      dist: trusty
-      language: python
-      python: 3.4
-      env: QT_SELECT=4 PYTHON=python
-
-    # Linux Qt5.2.1 Python2.7
-    - os: linux
-      dist: trusty
-      language: python
-      python: 2.7
-      env: QT_SELECT=5.2.1 PYTHON=python
-
-    # Linux Qt5.3.2 Python3.4
-    - os: linux
-      dist: trusty
-      language: python
-      python: 3.4
-      env: QT_SELECT=5.3.2 PYTHON=python
-
-    # Linux Qt5.4.2 Python2.7
-    - os: linux
-      dist: trusty
-      language: python
-      python: 2.7
-      env: QT_SELECT=5.4.2 PYTHON=python
-
-    # Linux Qt5.5.1 Python3.4
-    - os: linux
-      dist: trusty
-      language: python
-      python: 3.4
-      env: QT_SELECT=5.5.1 PYTHON=python
-
-    # Linux Qt5.6.3 Python2.7
-    - os: linux
-      dist: trusty
-      language: python
-      python: 2.7
-      env: QT_SELECT=5.6.3 PYTHON=python
-
-    # Linux Qt5.7.1 Python3.4
-    - os: linux
-      dist: trusty
-      language: python
-      python: 3.4
-      env: QT_SELECT=5.7.1 PYTHON=python
-
-    # Linux Qt5.8 Python2.7
-    - os: linux
-      dist: trusty
-      language: python
-      python: 2.7
-      env: QT_SELECT=5.8 PYTHON=python
-
-    # Linux Qt5.9.6 Python3.4
-    - os: linux
-      dist: trusty
-      language: python
-      python: 3.4
-      env: QT_SELECT=5.9.6 PYTHON=python
-
-    # Linux Qt5.10.1 Python2.7
-    - os: linux
-      dist: trusty
-      language: python
-      python: 2.7
-      env: QT_SELECT=5.10.1 PYTHON=python
-
-    # Linux Qt5.11.3 Python2.7
-    - os: linux
-      dist: xenial
-      language: python
-      python: 2.7
-      env: QT_SELECT=5.11.3 PYTHON=python
-
-    # Linux Qt5.12.8 Python3.5
-    - os: linux
-      dist: xenial
-      language: python
-      python: 3.5
-      env: QT_SELECT=5.12.8 PYTHON=python
-
-    # Linux Qt5.13.2 Python2.7
-    - os: linux
-      dist: bionic
-      language: python
-      python: 2.7
-      env: QT_SELECT=5.13.2 PYTHON=python
-
-    # Linux Qt5.14.2 Python3.8
-    - os: linux
-      dist: bionic
-      language: python
-      python: 3.8
-      env: QT_SELECT=5.14.2 PYTHON=python
-
-install:
-  - source ./ci/install_dependencies.sh # source required because of exports
-  - $PYTHON --version                   # check exact python version
-  - qmake --version                     # check exact Qt version
-
-script:
-  - flake8 client/funq server/funq_server
-  - cd client && $PYTHON setup.py develop && cd ..
-  - cd server && $PYTHON setup.py develop && cd ..
-  - cd server/tests && qmake QMAKE_CXXFLAGS="$FUNQ_CXXFLAGS" && make -j4 && cd ../..
-  - cd tests-functionnal/funq-test-app && qmake QMAKE_CXXFLAGS="$FUNQ_CXXFLAGS" && make -j4 && cd ../..
-  - cd client && $PYTHON setup.py test; cd ..
-  - make -C server/tests/ check
-  - cd tests-functionnal && nosetests && cd ..
diff --git a/README.rst b/README.rst
index 4d264bf..67c2194 100644
--- a/README.rst
+++ b/README.rst
@@ -80,7 +80,7 @@ You can instead get the sources and install it::
 
 For contributors, you may want to use **pip install -e** instead of
 **pip install** commands. Note that **virtualenv** is highly recommended,
-so you can easily manage multiple python2/python3/Qt4/Qt5 environments.
+so you can easily manage multiple python/Qt4/Qt5 environments.
 
 When installing funq-server from sources, you can create a server/setup.cfg
 file to specify the qmake path::
@@ -100,23 +100,16 @@ How does *funq* works
   executable called **funq** and a dynamic library **libFunq**. The
   **funq** executable allows to inject some code in a Qt application
   to start a TCP server that will allow to interact with the application.
-  This is currently not working with Python 3 on Windows, but you can still
-  build you application with libFunq as a workaround.
 
 - **funq** is a python package that offers an API to interact with a
   **libFunq** TCP server. It is the client side of the project, and uses
   nosetests to launch FUNctional Qt tests.
 
-Known restrictions
-==================
-
-Funq currently works with python >= 2.7 (it is fully compatible with python 3),
-Qt4 and Qt5 on GNU/Linux and macOS.
+Compatibility
+=============
 
-It also works on Windows, but only with Python 2.7 out of the box. With
-Python 3, the tested application has to be compiled with libFunq because the
-package *winappdbg* (needed for the DLL injection) is not available for Python 3
-(any help welcome!).
+Funq currently works with Python >= 3.5, Qt4 and Qt5 on GNU/Linux, macOS and
+Windows.
 
 Documentation
 =============
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 3d23a4f..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-build: off
-test: off
-deploy: off
-
-environment:
-  FUNQ_MAKE_PATH: "mingw32-make"
-  FUNQ_CXXFLAGS: "-Wall -Wextra -Werror"  # make CI fail on warnings
-  matrix:
-    # Test with oldest Qt version available on AppVeyor
-    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013
-      QT_DIR: C:/Qt/5.3/mingw482_32
-      MINGW_DIR: C:/Qt/Tools/mingw482_32
-    # Test with latest Qt LTS version
-    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-      QT_DIR: C:/Qt/5.12/mingw73_32
-      MINGW_DIR: C:/Qt/Tools/mingw730_32
-    # Test with latest Qt version available on AppVeyor
-    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
-      QT_DIR: C:/Qt/5.14/mingw73_32
-      MINGW_DIR: C:/Qt/Tools/mingw730_32
-
-init:
-  - set PATH=%QT_DIR%/bin;%MINGW_DIR%/bin;%PATH%
-
-install:
-  - cd client && python setup.py develop && cd ..
-  - cd server && python setup.py develop && cd ..
-  - cd server/tests && qmake QMAKE_CXXFLAGS="%FUNQ_CXXFLAGS%" && mingw32-make -j4 && cd ../../
-  - cd tests-functionnal/funq-test-app && qmake QMAKE_CXXFLAGS="%FUNQ_CXXFLAGS%" && mingw32-make -j4 && cd ../..
-
-build_script:
-  - cd client && python setup.py test && cd ..
-  - mingw32-make -C server/tests/ check
-  - cd tests-functionnal && nosetests && cd ..
diff --git a/ci/install_dependencies.sh b/ci/install_dependencies.sh
deleted file mode 100755
index d3e0342..0000000
--- a/ci/install_dependencies.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env bash
-
-# use multiple cores for building
-export MAKEFLAGS="-j8"
-
-# Install dependencies on Linux
-if [[ "${TRAVIS_OS_NAME-}" == "linux" ]]
-then
-
-  # common
-  sudo apt-get update -q
-  sudo apt-get install -q build-essential xvfb
-
-  # qt
-  if [[ "$QT_SELECT" == "4" ]]
-  then
-    sudo apt-get install -q libqt4-dev qt4-dev-tools
-  else
-    # Add corresponding PPA from https://launchpad.net/~beineri
-    MAJOR=`echo "$QT_SELECT" | cut -d'.' -f 1`
-    MINOR=`echo "$QT_SELECT" | cut -d'.' -f 2`
-    PATCH=`echo "$QT_SELECT" | cut -d'.' -f 3`
-    PREFIX="" && [[ "$MINOR" -ge 10 ]] && PREFIX="-"
-    SEPARATOR="" && [[ "$MINOR" -ge 10 ]] && SEPARATOR="."
-    CODENAME=`lsb_release -sc`
-    sudo add-apt-repository "ppa:beineri/opt-qt${PREFIX}${MAJOR}${SEPARATOR}${MINOR}${SEPARATOR}${PATCH}-$CODENAME" -y
-    sudo apt-get update -q
-    sudo apt-get install -q "qt${MAJOR}${MINOR}base" "qt${MAJOR}${MINOR}tools" "qt${MAJOR}${MINOR}declarative" libglu1-mesa-dev
-    source "/opt/qt${MAJOR}${MINOR}/bin/qt${MAJOR}${MINOR}-env.sh"
-  fi
-
-  # python packages
-  pip install flake8
-
-  # xvfb is only needed on Trusty, see deteils here:
-  # https://docs.travis-ci.com/user/gui-and-headless-browsers/#using-xvfb-to-run-tests-that-require-a-gui
-  if [[ "`lsb_release -sc`" == "trusty" ]]
-  then
-    export DISPLAY=:99.0
-    sh -e /etc/init.d/xvfb start
-  fi
-
-
-# Install dependencies on OS X
-elif [[ "${TRAVIS_OS_NAME-}" == "osx" ]]
-then
-
-  # ccache
-  brew update
-  brew install ccache
-  export PATH="/usr/local/opt/ccache/libexec:$PATH"
-
-  # qt
-  brew install qt5
-  brew link --force qt5
-
-  # python packages
-  pip${PYTHON/python/} install --user flake8
-  export PATH="$PATH:`$PYTHON -m site --user-base`/bin"
-
-fi
diff --git a/client/funq/aliases.py b/client/funq/aliases.py
index 03bea0f..5494598 100644
--- a/client/funq/aliases.py
+++ b/client/funq/aliases.py
@@ -37,7 +37,7 @@
 complete widget's paths.
 """
 
-from ConfigParser import ConfigParser
+from configparser import ConfigParser
 import collections
 from funq.errors import HooqAliasesInvalidLineError, HooqAliasesKeyError
 
diff --git a/client/funq/client.py b/client/funq/client.py
index 5ce8ccb..73b6f99 100644
--- a/client/funq/client.py
+++ b/client/funq/client.py
@@ -55,14 +55,6 @@
 LOG = logging.getLogger('funq.client')
 
 
-# python 3 compatibility
-# https://stackoverflow.com/questions/11301138/how-to-check-if-variable-is-string-with-python-2-and-3-compatibility)
-try:
-    basestring
-except NameError:
-    basestring = str
-
-
 class FunqClient(object):
 
     """
@@ -82,7 +74,7 @@ def __init__(self, host=None, port=None, aliases=None,
 
         if aliases is None:
             aliases = HooqAliases()
-        elif isinstance(aliases, basestring):
+        elif isinstance(aliases, str):
             aliases = HooqAliases.from_file(aliases)
         elif not isinstance(aliases, HooqAliases):
             raise TypeError("aliases must be None or str or an"
@@ -156,8 +148,8 @@ def send_command(self, action, **kwargs):
         header = f.readline()
         if not header:
             raise FunqError("NoResponseFromApplication",
-                            u"Pas de réponse de l'application testée -"
-                            u" probablement un crash.")
+                            "Pas de réponse de l'application testée -"
+                            " probablement un crash.")
         to_read = int(header)
         response = json.loads(f.read(to_read).decode('utf-8'))
         if response.get('success') is False:
@@ -166,7 +158,7 @@ def send_command(self, action, **kwargs):
 
     def quit(self):
         """
-        Ask the tested application to quit by calling qApp->quit().
+        Ask the tested application to quit by calling qApp->exit().
         """
         self._raw_send('quit', {})
 
@@ -323,7 +315,7 @@ def dump_widgets_list(self, stream='widgets_list.json',
         """
         Write in a file the result of :meth:`widgets_list`.
         """
-        if isinstance(stream, basestring):
+        if isinstance(stream, str):
             stream = open(stream, 'w')
         json.dump(self.widgets_list(with_properties=with_properties),
                   stream, sort_keys=True, indent=4, separators=(',', ': '))
@@ -333,7 +325,7 @@ def take_screenshot(self, stream='screenshot.png', format_='PNG'):
         Take a screenshot of the active desktop.
         """
         data = self.send_command('grab', format=format_)
-        if isinstance(stream, basestring):
+        if isinstance(stream, str):
             stream = open(stream, 'wb')
         raw = base64.standard_b64decode(data['data'])
         stream.write(raw)  # pylint: disable=E1103
@@ -516,7 +508,7 @@ def terminate(self):
                     self._process = None
                 else:
                     # try to exit nicely the tested application process
-                    # with a call to qApp->quit().
+                    # with a call to qApp->exit().
                     LOG.info("Closing tested application [%s].",
                              self._process.pid)
                     try:
diff --git a/client/funq/errors.py b/client/funq/errors.py
index 3c5b9ba..a7dc93d 100644
--- a/client/funq/errors.py
+++ b/client/funq/errors.py
@@ -49,7 +49,7 @@ class FunqError(Exception):
     def __init__(self, classname, desc):
         self.classname = classname
         self.desc = desc
-        Exception.__init__(self, u"{classname}: {desc}".format(
+        Exception.__init__(self, "{classname}: {desc}".format(
             classname=classname,
             desc=desc))
 
diff --git a/client/funq/models.py b/client/funq/models.py
index a780a90..66afc9d 100644
--- a/client/funq/models.py
+++ b/client/funq/models.py
@@ -41,14 +41,6 @@
 import base64
 
 
-# python 3 compatibility
-# https://stackoverflow.com/questions/11301138/how-to-check-if-variable-is-string-with-python-2-and-3-compatibility)
-try:
-    basestring
-except NameError:
-    basestring = str
-
-
 class TreeItem(object):  # pylint: disable=R0903
 
     """
@@ -64,7 +56,7 @@ def create(cls, client, data):
         """
         self = cls()
         self.client = client
-        for k, v in data.iteritems():
+        for k, v in data.items():
             if k != 'items':
                 setattr(self, k, v)
         self.items = [cls.create(client, d) for d in data.get('items', [])]
@@ -128,7 +120,7 @@ def __new__(mcs, name, bases, attrs):
         return cls
 
 
-class Object(object):
+class Object(object, metaclass=WidgetMetaClass):
 
     """
     Allow to manipulate a QObject or derived.
@@ -141,8 +133,6 @@ class Object(object):
                   in inheritance order (ie 'QObject' is last)
                   [type : list(str)]
     """
-    __metaclass__ = WidgetMetaClass
-
     oid = None
     client = None
     path = None
@@ -161,7 +151,7 @@ def create(cls, client, data):
                 break
 
         self = cls()
-        for k, v in data.iteritems():
+        for k, v in data.items():
             setattr(self, k, v)
         setattr(self, 'client', client)
         return self
@@ -209,7 +199,7 @@ def wait_for_properties(self, props, timeout=10.0, timeout_interval=0.1):
         """
         def check_props():
             properties = self.properties()
-            for k, v in props.iteritems():
+            for k, v in props.items():
                 if properties.get(k) != v:
                     return False
             return True
@@ -852,7 +842,7 @@ def dump_gitems(self, stream='gitems.json'):
         Write in a file the list of graphics items.
         """
         data = self.client.send_command('graphicsitems', oid=self.oid)
-        if isinstance(stream, basestring):
+        if isinstance(stream, str):
             stream = open(stream, 'w')
         json.dump(data,
                   stream, sort_keys=True, indent=4, separators=(',', ': '))
@@ -867,7 +857,7 @@ def grab_scene(self, stream, format_="PNG"):
         data = self.client.send_command('grab_graphics_view', format=format_,
                                         oid=self.oid)
         has_to_be_closed = False
-        if isinstance(stream, basestring):
+        if isinstance(stream, str):
             stream = open(stream, 'wb')
             has_to_be_closed = True
         raw = base64.standard_b64decode(data['data'])
@@ -895,7 +885,7 @@ def set_current_text(self, text):
         """
         Define the text of the combobox, ensuring that it is a possible value.
         """
-        if not isinstance(text, basestring):
+        if not isinstance(text, str):
             raise TypeError('the text parameter must be a string'
                             ' - got %s' % type(text))
         column = self.properties()['modelColumn']
diff --git a/client/funq/noseplugin.py b/client/funq/noseplugin.py
index 22402a4..e3690bd 100644
--- a/client/funq/noseplugin.py
+++ b/client/funq/noseplugin.py
@@ -42,7 +42,7 @@
 from funq.screenshoter import ScreenShoter
 from funq import tools
 from nose.plugins import Plugin
-from ConfigParser import ConfigParser
+from configparser import ConfigParser
 import os
 import codecs
 import logging
@@ -149,7 +149,7 @@ def configure(self, options, cfg):
         FunqPlugin._instance = self
 
     def beforeTest(self, test):
-        message = u"Starting test `%s`" % test.id()
+        message = "Starting test `%s`" % test.id()
         lines = message_with_sep(message)
         for line in lines:
             LOG.info(line)
@@ -160,7 +160,7 @@ def beforeTest(self, test):
                 f.write('\n')
 
     def afterTest(self, test):
-        message = u"Ending test `%s`" % test.id()
+        message = "Ending test `%s`" % test.id()
         lines = message_with_sep(message)
         for line in lines:
             LOG.info(line)
@@ -176,7 +176,7 @@ def describeTest(self, test):
     def take_screenshot(self, test):
         if isinstance(test, MultiFunqTestCase):
             if test.__app_config__:
-                for k, v in test.__app_config__.iteritems():
+                for k, v in test.__app_config__.items():
                     if v.screenshot_on_error:
                         self.screenshoter.take_screenshot(
                             test.funq[k],
diff --git a/client/funq/screenshoter.py b/client/funq/screenshoter.py
index 25e19c8..45a3619 100644
--- a/client/funq/screenshoter.py
+++ b/client/funq/screenshoter.py
@@ -67,7 +67,7 @@ def take_screenshot(self, funqclient, longname):
         if not os.path.isdir(self.working_folder):
             os.makedirs(self.working_folder)
 
-        bname = '{0}.png'.format(self.counter.next())
+        bname = '{0}.png'.format(next(self.counter))
         fname = os.path.join(self.working_folder, bname)
 
         try:
@@ -75,9 +75,9 @@ def take_screenshot(self, funqclient, longname):
         except (SystemExit, KeyboardInterrupt):
             raise
         except Exception:
-            LOG.exception(u"impossible de prendre un screenshot pour"
-                          u" %s", longname)
+            LOG.exception("impossible de prendre un screenshot pour"
+                          " %s", longname)
             return
 
         with codecs.open(self.txt_file_path, "a", "utf-8") as f:
-            f.write(u"{0}: {1}\n".format(bname, longname))
+            f.write("{0}: {1}\n".format(bname, longname))
diff --git a/client/setup.py b/client/setup.py
index a4557a9..24c1399 100644
--- a/client/setup.py
+++ b/client/setup.py
@@ -4,13 +4,14 @@
 import re
 import sys
 
-if sys.version_info < (2, 7):
-    sys.exit("Python version must be > 2.7")
+if sys.version_info < (3, 5):
+    sys.exit("Python version must be >= 3.5")
 
 
 def read(*paths):
     this_dir = os.path.dirname(os.path.realpath(__file__))
-    return open(os.path.join(this_dir, *paths)).read()
+    content = open(os.path.join(this_dir, *paths), "rb").read()
+    return content.decode("utf-8")
 
 
 version = re.search("__version__ = '(.+)'", read('funq/__init__.py')).group(1)
@@ -29,7 +30,6 @@ def read(*paths):
     version=version,
     packages=find_packages(),
     zip_safe=False,
-    use_2to3=True,
     test_suite='funq.tests.create_test_suite',
     install_requires=install_requires,
     package_data={'funq': ['aliases-gkits.conf']},
diff --git a/doc-dev/server.rst b/doc-dev/server.rst
index c9aa907..9b65784 100644
--- a/doc-dev/server.rst
+++ b/doc-dev/server.rst
@@ -43,7 +43,7 @@ player.cpp:
   
   QtJson::JsonObject Player::quit(const QtJson::JsonObject &) {
       if (qApp) {
-          qApp->quit();
+          qApp->exit();
       }
       QtJson::JsonObject result;
       return result;
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
new file mode 100644
index 0000000..12e0e72
--- /dev/null
+++ b/server/CMakeLists.txt
@@ -0,0 +1,93 @@
+# CMake version configuration
+cmake_minimum_required(VERSION 3.5...3.19)
+if(${CMAKE_VERSION} VERSION_LESS 3.12)
+  cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
+endif()
+
+# Define project
+project(funq LANGUAGES CXX)
+
+# Global options
+option(BUILD_TESTS "Build tests." OFF)
+option(BUILD_DISALLOW_WARNINGS
+       "Disallow compiler warnings during build (build with -Werror)." OFF
+)
+
+# Create a release build by default
+# (Code based on https://blog.kitware.com/cmake-and-the-default-build-type/)
+set(default_build_type "Release")
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  # Set the build type
+  message(
+    STATUS
+      "Setting build type to '${default_build_type}' as none was specified."
+  )
+  set(CMAKE_BUILD_TYPE
+      "${default_build_type}"
+      CACHE STRING "Choose the type of build." FORCE
+  )
+
+  # Set the possible values of build type for cmake-gui
+  set_property(
+    CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel"
+                                    "RelWithDebInfo"
+  )
+endif()
+
+# Auto-include current directory
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+# If Qt version is not explicitly specified, automatically determine it
+if(NOT QT_MAJOR_VERSION)
+  find_package(Qt NAMES Qt5 Qt6 REQUIRED COMPONENTS Core)
+  set(QT_MAJOR_VERSION ${Qt_VERSION_MAJOR})
+endif()
+set(QT "Qt${QT_MAJOR_VERSION}")
+
+# Use C++11 (Qt5) or C++17 (Qt6)
+if(QT_MAJOR_VERSION EQUAL 6)
+  set(CMAKE_CXX_STANDARD 17)
+else()
+  set(CMAKE_CXX_STANDARD 11)
+endif()
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+# Set common compile options
+add_compile_options(-Wall -Wextra -Wpedantic)
+if(BUILD_DISALLOW_WARNINGS)
+  message(STATUS "Building with -Werror")
+  add_compile_options(-Werror)
+endif()
+if(WIN32)
+  add_definitions(-DUNICODE -D_UNICODE)
+endif()
+
+# Disable Qt functions which are deprecated in Qt <= 5.0 to enforce us
+# migrating away from them. Always set this to the lowest Qt version we want
+# to support.
+add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050000)
+
+# Not sure what we should do with deprecation warnings. Better keeping
+# backwards compatibility with already existing & supported environments
+# instead of forward compatibility with not yet existing environments, no?
+add_definitions(-DQT_NO_DEPRECATED_WARNINGS)
+
+# Find Qt
+find_package(
+  ${QT}
+  REQUIRED COMPONENTS Core Gui Network Widgets Test
+  OPTIONAL_COMPONENTS Quick
+)
+set(QT_VERSION "${${QT}_VERSION}")
+set(WITH_QTQUICK "${${QT}Quick_FOUND}")
+message(STATUS "Building with Qt ${QT_VERSION}")
+message(STATUS "QtQuick found: ${WITH_QTQUICK}")
+
+# Add subprojects
+add_subdirectory(libFunq)
+if(BUILD_TESTS)
+  add_subdirectory(player_tester)
+  add_subdirectory(tests/libFunq)
+  add_subdirectory(tests/protocole)
+endif()
diff --git a/server/funq.pro b/server/funq.pro
deleted file mode 100644
index 8a66a3f..0000000
--- a/server/funq.pro
+++ /dev/null
@@ -1,3 +0,0 @@
-TEMPLATE = subdirs
-
-SUBDIRS = libFunq
diff --git a/server/funq_server/runner.py b/server/funq_server/runner.py
index 3c13bf3..3dcb955 100644
--- a/server/funq_server/runner.py
+++ b/server/funq_server/runner.py
@@ -65,7 +65,7 @@ def __init__(self):
         self.system = platform.system()
 
     def _parse_args(self, argv=None):
-        desc = u"""Start a QT application with a libFunq server injected."""
+        desc = """Start a QT application with a libFunq server injected."""
         parser = argparse.ArgumentParser(description=desc)
         parser.add_argument('-v', '--version', action='version',
                             version=funq_server.__version__)
@@ -81,7 +81,7 @@ def _parse_args(self, argv=None):
     def _find_library(self):
         this_dir = os.path.dirname(os.path.realpath(__file__))
         if self.system == 'Windows':
-            library_name = 'Funq.dll'
+            library_name = 'libFunq.dll'
         elif self.system == 'Darwin':
             library_name = 'libFunq.dylib'
         else:
diff --git a/server/funq_server/runner_win.py b/server/funq_server/runner_win.py
index 14f154a..bfab975 100644
--- a/server/funq_server/runner_win.py
+++ b/server/funq_server/runner_win.py
@@ -33,44 +33,191 @@
 # knowledge of the CeCILL v2.1 license and that you accept its terms.
 
 from funq_server.runner import RunnerInjector
-import winappdbg
+from ctypes import wintypes, byref
+import ctypes
 import time
 
+# Useful resources regarding DLL injection:
+#
+# - https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices  # noqa: E501
+# - https://blog.nettitude.com/uk/dll-injection-part-two
+# - https://stackoverflow.com/questions/17392721/error-invalid-parameter-error-57-when-calling-createremotethread-with-python-3-2/17524073#17524073  # noqa: E501
+# - https://stackoverflow.com/questions/27332509/createremotethread-on-loadlibrary-and-get-the-hmodule-back  # noqa: E501
+# - https://github.com/numaru/injector
+
+# Constants from Windows API documentation.
+PROCESS_CREATE_THREAD = 0x0002
+PROCESS_VM_OPERATION = 0x0008
+PROCESS_VM_WRITE = 0x0020
+MEM_COMMIT = 0x1000
+MEM_RESERVE = 0x2000
+MEM_RELEASE = 0x8000
+PAGE_READWRITE = 0x04
 
-class WindowsRunnerInjector(RunnerInjector):
-    max_wait = 20  # 20 seconds to let the process starts well
 
+class WindowsRunnerInjector(RunnerInjector):
     def start_subprocess(self):
+        # Start the process.
         RunnerInjector.start_subprocess(self)
-        # wait for the process to be be running...
-        proc = winappdbg.Process(self._proc.pid)
-        start = time.time()
-        while True:
-            # wait for QT to be loaded
-            if self._proc.poll() is not None:
-                raise RuntimeError("The process has finished with an error"
-                                   " code of %d before we could hook it with"
-                                   " funq." % self._proc.returncode)
-
-            if start + self.max_wait < time.time():
-                self._proc.terminate()
-                raise RuntimeError("Error while waiting for subprocess to be"
-                                   " launched. Is the executable linked to"
-                                   " qt4 ?")
+
+        # Unfortunately we do not know when the process startup is really
+        # complete and Qt libraries are loaded. When starting the injection
+        # too early, it does not work 100% reliable (in rare cases, the
+        # process freezes or crashes). When slightly delaying the injection,
+        # it seems to work more reliable. One seconds seems to be a safe
+        # choice to also make it reliable if the system is very busy.
+        # Hopefully someone finds a better way some day (without delay)...
+        time.sleep(1.0)
+
+        # Check if the process is still running.
+        if self._proc.poll() is not None:
+            raise RuntimeError("The process has finished with an error"
+                               " code of %d before we could hook it with"
+                               " funq." % self._proc.returncode)
+
+        # Inject the DLL now.
+        try:
+            self._inject_dll(self._proc.pid, self.library_path)
+        except Exception:
             try:
-                proc.scan_modules()
-            except WindowsError:
-                # Following exception occurs sometimes, but it still works fine
-                # so let's ignore it: WindowsError: [Error 299] Only part of a
-                # ReadProcessMemory or WriteProcessMemory request was completed
-                pass
-            lib_names = [lib.get_name() for lib in proc.iter_modules()]
-            qt_lib_names = ['qtguid4', 'qtgui4', 'qt5guid', 'qt5gui']
-            if len(set(qt_lib_names).intersection(set(lib_names))) > 0:
-                break
-            time.sleep(0.01)
-
-        # wait a bit, and hope that QT is now really initialized !
-        time.sleep(1)
-        # we can inject the dll
-        proc.inject_dll(self.library_path)
+                # Injection failed, try to terminate the process.
+                self._proc.terminate()
+            except Exception as e:
+                print("Failed to terminate process: {}".format(e))
+            raise
+
+    def _inject_dll(self, pid, dll_path):
+        # Get handle to kernel32.dll and prepare functions.
+        kernel32 = ctypes.WinDLL('kernel32.dll', use_last_error=True)
+        kernel32.OpenProcess.restype = wintypes.HANDLE
+        kernel32.OpenProcess.argtypes = (
+            wintypes.DWORD,  # dwDesiredAccess
+            wintypes.BOOL,   # bInheritHandle
+            wintypes.DWORD,  # dwProcessId
+        )
+        kernel32.VirtualAllocEx.restype = wintypes.LPVOID
+        kernel32.VirtualAllocEx.argtypes = (
+            wintypes.HANDLE,  # hProcess
+            wintypes.LPVOID,  # lpAddress
+            ctypes.c_size_t,  # dwSize
+            wintypes.DWORD,   # flAllocationType
+            wintypes.DWORD,   # flProtect
+        )
+        kernel32.VirtualFreeEx.restype = wintypes.BOOL
+        kernel32.VirtualFreeEx.argtypes = (
+            wintypes.HANDLE,  # hProcess
+            wintypes.LPVOID,  # lpAddress
+            ctypes.c_size_t,  # dwSize
+            wintypes.DWORD,   # dwFreeType
+        )
+        kernel32.WriteProcessMemory.restype = wintypes.BOOL
+        kernel32.WriteProcessMemory.argtypes = (
+            wintypes.HANDLE,                  # hProcess
+            wintypes.LPVOID,                  # lpBaseAddress
+            wintypes.LPCVOID,                 # lpBuffer
+            ctypes.c_size_t,                  # nSize
+            ctypes.POINTER(ctypes.c_size_t),  # lpNumberOfBytesWritten _Out_
+        )
+        kernel32.CreateRemoteThread.restype = wintypes.LPVOID
+        kernel32.CreateRemoteThread.argtypes = (
+            wintypes.HANDLE,         # hProcess
+            wintypes.LPVOID,         # lpThreadAttributes
+            ctypes.c_size_t,         # dwStackSize
+            wintypes.LPVOID,         # lpStartAddress
+            wintypes.LPVOID,         # lpParameter
+            wintypes.DWORD,          # dwCreationFlags
+            wintypes.LPDWORD,        # lpThreadId _Out_
+        )
+        kernel32.WaitForSingleObject.restype = wintypes.DWORD
+        kernel32.WaitForSingleObject.argtypes = (
+            wintypes.HANDLE,  # hHandle
+            wintypes.DWORD,   # dwMilliseconds
+        )
+        kernel32.GetExitCodeThread.restype = wintypes.BOOL
+        kernel32.GetExitCodeThread.argtypes = (
+            wintypes.HANDLE,   # hThread
+            wintypes.LPDWORD,  # lpExitCode
+        )
+        kernel32.CloseHandle.restype = wintypes.BOOL
+        kernel32.CloseHandle.argtypes = (
+            wintypes.HANDLE,  # hObject
+        )
+
+        # Start the injection.
+        size = (len(dll_path) + 1) * ctypes.sizeof(wintypes.WCHAR)
+        h_process = None
+        adr_path = None
+        h_thread = None
+        try:
+            # Get handle to the running process.
+            h_process = kernel32.OpenProcess(
+                PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |
+                PROCESS_VM_WRITE, False, pid)
+            if h_process is None:
+                self._raise_windows_error("OpenProcess()", h_process)
+
+            # Allocate memory for the DLL path.
+            adr_path = kernel32.VirtualAllocEx(
+                h_process, None, size, MEM_COMMIT | MEM_RESERVE,
+                PAGE_READWRITE)
+            if adr_path is None:
+                self._raise_windows_error("VirtualAllocEx()", adr_path)
+
+            # Write DLL path into the allocated memory region.
+            success = kernel32.WriteProcessMemory(
+                h_process, adr_path, dll_path, size, None)
+            if not success:
+                self._raise_windows_error("WriteProcessMemory()", success)
+
+            # Create and start new thread in the process. The entry point of
+            # the new thread is LoadLibraryW() with our DLL path as argument.
+            h_thread = kernel32.CreateRemoteThread(
+                h_process, None, 0, kernel32.LoadLibraryW, adr_path, 0, None)
+            if h_thread is None:
+                self._raise_windows_error("CreateRemoteThread()", h_thread)
+
+            # Wait (with 10s timeout) until the thread exited, i.e. our DLL
+            # injection either succeeded or failed.
+            error = kernel32.WaitForSingleObject(h_thread, 10000)
+            if error:
+                self._raise_windows_error("WaitForSingleObject()", error)
+
+            # Get the exit code of our thread, which corresponds to the return
+            # value of LoadLibraryW() so we can check if the DLL was loaded
+            # successfully or not.
+            libfunq_handle = wintypes.DWORD(0)
+            success = kernel32.GetExitCodeThread(
+                h_thread, byref(libfunq_handle))
+            if not success:
+                self._raise_windows_error("GetExitCodeThread()", success)
+            if not libfunq_handle:
+                self._raise_windows_error("LoadLibraryW()", libfunq_handle)
+        finally:
+            if adr_path is not None:
+                success = kernel32.VirtualFreeEx(h_process, adr_path, 0,
+                                                 MEM_RELEASE)
+                if not success:
+                    self._raise_windows_error("VirtualFreeEx()", success)
+
+            if h_thread is not None:
+                success = kernel32.CloseHandle(h_thread)
+                if not success:
+                    self._raise_windows_error("CloseHandle()", success)
+
+            if h_process is not None:
+                success = kernel32.CloseHandle(h_process)
+                if not success:
+                    self._raise_windows_error("CloseHandle()", success)
+
+    def _raise_windows_error(self, function_name, return_value):
+        """
+        Helper function to raise an error returned by a WIN32 API function.
+        """
+        last_error = ctypes.get_last_error()
+        win_error = ctypes.WinError(last_error)
+        message = "Failed to inject DLL! "
+        message += "{} returned 0x{:X}. ".format(function_name, return_value)
+        message += "The last error is 0x{:X} ({}). ".format(
+            last_error, str(win_error))
+        message += "Maybe x86/x64 mismatch between python.exe and Qt DLLs?"
+        raise RuntimeError(message)
diff --git a/server/libFunq/CMakeLists.txt b/server/libFunq/CMakeLists.txt
new file mode 100644
index 0000000..f578646
--- /dev/null
+++ b/server/libFunq/CMakeLists.txt
@@ -0,0 +1,47 @@
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC OFF)
+set(CMAKE_AUTORCC OFF)
+
+set(
+  FUNQ_SOURCES
+  delayedresponse.cpp
+  delayedresponse.h
+  dragndropresponse.cpp
+  dragndropresponse.h
+  funq.cpp
+  funq.h
+  json.cpp
+  json.h
+  jsonclient.cpp
+  jsonclient.h
+  objectpath.cpp
+  objectpath.h
+  pick.cpp
+  pick.h
+  player.cpp
+  protocole.cpp
+  protocole.h
+  shortcutresponse.cpp
+  shortcutresponse.h
+)
+if(WIN32)
+  list(APPEND FUNQ_SOURCES WindowsInjector.cpp WindowsInjector.h)
+else()
+  list(APPEND FUNQ_SOURCES ldPreloadInjector.cpp)
+endif()
+
+set(
+  FUNQ_DEPENDENCIES
+  ${QT}::Core
+  ${QT}::Gui
+  ${QT}::Network
+  ${QT}::Widgets
+  ${QT}::Test
+  $<$<BOOL:${WITH_QTQUICK}>:${QT}::Quick>
+)
+
+add_library(FunqStatic STATIC ${FUNQ_SOURCES})
+target_link_libraries(FunqStatic PUBLIC ${FUNQ_DEPENDENCIES})
+
+add_library(Funq SHARED ${FUNQ_SOURCES})
+target_link_libraries(Funq PUBLIC ${FUNQ_DEPENDENCIES})
diff --git a/server/protocole/delayedresponse.cpp b/server/libFunq/delayedresponse.cpp
similarity index 100%
rename from server/protocole/delayedresponse.cpp
rename to server/libFunq/delayedresponse.cpp
diff --git a/server/protocole/delayedresponse.h b/server/libFunq/delayedresponse.h
similarity index 100%
rename from server/protocole/delayedresponse.h
rename to server/libFunq/delayedresponse.h
diff --git a/server/protocole/json.cpp b/server/libFunq/json.cpp
similarity index 100%
rename from server/protocole/json.cpp
rename to server/libFunq/json.cpp
diff --git a/server/protocole/json.h b/server/libFunq/json.h
similarity index 100%
rename from server/protocole/json.h
rename to server/libFunq/json.h
diff --git a/server/protocole/jsonclient.cpp b/server/libFunq/jsonclient.cpp
similarity index 97%
rename from server/protocole/jsonclient.cpp
rename to server/libFunq/jsonclient.cpp
index 5bd279d..64e0014 100644
--- a/server/protocole/jsonclient.cpp
+++ b/server/libFunq/jsonclient.cpp
@@ -76,11 +76,7 @@ void JsonClient::onMessageReceived() {
     for (int i = metaObject()->methodOffset(); i < metaObject()->methodCount();
          ++i) {
         method = metaObject()->method(i);
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
         QString signature = method.methodSignature();
-#else
-        QString signature = method.signature();
-#endif
         if (signature.startsWith(action) &&
             signature.at(action.length()) == '(') {
             success = true;
diff --git a/server/protocole/jsonclient.h b/server/libFunq/jsonclient.h
similarity index 100%
rename from server/protocole/jsonclient.h
rename to server/libFunq/jsonclient.h
diff --git a/server/libFunq/libFunq.pri b/server/libFunq/libFunq.pri
deleted file mode 100644
index 49654de..0000000
--- a/server/libFunq/libFunq.pri
+++ /dev/null
@@ -1,34 +0,0 @@
-include(../protocole/protocole.pri)
-
-greaterThan(QT_MAJOR_VERSION, 4): QT += widgets quick
-QT += testlib
-
-INCLUDEPATH += $${PWD}
-
-SOURCES += $${PWD}/player.cpp \
-    $${PWD}/funq.cpp \
-    $${PWD}/objectpath.cpp \
-    $${PWD}/dragndropresponse.cpp \
-    $${PWD}/shortcutresponse.cpp \
-    $${PWD}/pick.cpp
-
-HEADERS += $${PWD}/player.h \
-    $${PWD}/funq.h \
-    $${PWD}/objectpath.h \
-    $${PWD}/dragndropresponse.h \
-    $${PWD}/shortcutresponse.h \
-    $${PWD}/pick.h
-
-win32 {
-
-HEADERS += \
-    $${PWD}/WindowsInjector.h
-
-SOURCES += $${PWD}/WindowsInjector.cpp
-
-} else {
-
-SOURCES += \
-    $${PWD}/ldPreloadInjector.cpp
-
-}
diff --git a/server/libFunq/libFunq.pro b/server/libFunq/libFunq.pro
deleted file mode 100644
index 5365438..0000000
--- a/server/libFunq/libFunq.pro
+++ /dev/null
@@ -1,14 +0,0 @@
-TEMPLATE = lib
-TARGET = Funq
-
-DESTDIR     = ../bin
-
-isEmpty(PREFIX) { PREFIX = /usr/local }
-target.path = $$PREFIX/bin/
-INSTALLS += target
-
-# Disable deprecation warnings since we want to be backward compatible to Qt4
-# and thus have to use some methods which are deprecated in recent Qt versions.
-DEFINES += QT_NO_DEPRECATED_WARNINGS=1
-
-include(libFunq.pri)
diff --git a/server/libFunq/objectpath.cpp b/server/libFunq/objectpath.cpp
index 67f21b2..274e727 100644
--- a/server/libFunq/objectpath.cpp
+++ b/server/libFunq/objectpath.cpp
@@ -38,13 +38,13 @@ knowledge of the CeCILL v2.1 license and that you accept its terms.
 #include <QGraphicsItem>
 #include <QGraphicsView>
 #include <QWidget>
+#include <QWindow>
 
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
+#ifdef QT_QUICK_LIB
 #include <QQmlContext>
 #include <QQmlEngine>
 #include <QQuickItem>
 #include <QQuickWindow>
-#include <QWindow>
 #endif
 
 /**
@@ -119,13 +119,11 @@ QObject * ObjectPath::findObject(const QString & path) {
             }
         }
         // did not find any ? - let's try on windows (qtquick)
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
         Q_FOREACH (QWindow * window, QApplication::topLevelWindows()) {
             if (objectName(window) == name) {
                 return window;
             }
         }
-#endif
         return 0;
     } else {
         parent = findObject(parts.join(separator));
diff --git a/server/libFunq/pick.cpp b/server/libFunq/pick.cpp
index 4f8ca2e..ee1c146 100644
--- a/server/libFunq/pick.cpp
+++ b/server/libFunq/pick.cpp
@@ -75,7 +75,7 @@ void print_object_props(QObject * object, QTextStream & stream) {
         QMetaProperty property = object->metaObject()->property(i);
         QString strValue = property.read(object).toString();
         if (!strValue.isEmpty()) {
-            stream << "\t" << property.name() << ": " << strValue << endl;
+            stream << "\t" << property.name() << ": " << strValue << '\n';
         }
     }
 }
@@ -85,7 +85,7 @@ void PickFormatter::handle(QObject * object, const QPoint & pos) {
                        .arg(ObjectPath::objectPath(object))
                        .arg(pos.x())
                        .arg(pos.y());
-    m_stream << path << endl;
+    m_stream << path << '\n';
     if (m_showProperties) {
         print_object_props(object, m_stream);
     }
@@ -96,10 +96,12 @@ void PickFormatter::handle(QObject * object, const QPoint & pos) {
         QObject * qitem = dynamic_cast<QObject *>(item);
         if (item) {
             m_stream << "GITEM: `" << ObjectPath::graphicsItemId(item)
-                     << "` (QObject: " << (qitem != 0) << ")" << endl;
+                     << "` (QObject: " << (qitem != 0) << ")" << '\n';
             if (qitem) {
                 print_object_props(qitem, m_stream);
             }
         }
     }
+
+    m_stream.flush();
 }
diff --git a/server/libFunq/player.cpp b/server/libFunq/player.cpp
index 8ad2367..d41f004 100644
--- a/server/libFunq/player.cpp
+++ b/server/libFunq/player.cpp
@@ -44,7 +44,6 @@ knowledge of the CeCILL v2.1 license and that you accept its terms.
 #include <QApplication>
 #include <QBuffer>
 #include <QComboBox>
-#include <QDesktopWidget>
 #include <QGraphicsItem>
 #include <QGraphicsView>
 #include <QHeaderView>
@@ -56,11 +55,17 @@ knowledge of the CeCILL v2.1 license and that you accept its terms.
 #include <QTimer>
 #include <QTreeView>
 #include <QWidget>
+#include <QWindow>
+
+#if QT_VERSION_MAJOR >= 6
+#include <QScreen>
+#else
+#include <QDesktopWidget>
+#endif
 
 #ifdef QT_QUICK_LIB
 #include <QQuickItem>
 #include <QQuickWindow>
-#include <QWindow>
 #endif
 
 using namespace ObjectPath;
@@ -133,8 +138,13 @@ QString item_model_path(QAbstractItemModel * model, const QModelIndex & item) {
         parent = model->parent(parent);
     }
     // reverse list
-    for (int k = 0, s = path.size(), max = (s / 2); k < max; k++)
+    for (int k = 0, s = path.size(), max = (s / 2); k < max; k++) {
+#if QT_VERSION_MAJOR >= 6
+        path.swapItemsAt(k, s - (1 + k));
+#else
         path.swap(k, s - (1 + k));
+#endif
+    }
     return path.join("/");
 }
 
@@ -225,11 +235,7 @@ void dump_graphics_items(const QList<QGraphicsItem *> & items,
             outitem["classes"] = classes;
             outitem["objectname"] = itemObject->objectName();
         }
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
         dump_graphics_items(item->childItems(), viewid, outitem);
-#else
-        dump_graphics_items(item->children(), viewid, outitem);
-#endif
         outitems << outitem;
     }
     out["items"] = outitems;
@@ -268,12 +274,8 @@ QtJson::JsonObject Player::list_actions(const QtJson::JsonObject &) {
          ++i) {
         QMetaMethod method = metaObject->method(i);
         if (method.methodType() == QMetaMethod::Slot) {
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
             methods << QString::fromLatin1(
                 metaObject->method(i).methodSignature());
-#else
-            methods << QString::fromLatin1(metaObject->method(i).signature());
-#endif
         }
     }
     QtJson::JsonObject result;
@@ -339,30 +341,24 @@ QtJson::JsonObject Player::active_widget(const QtJson::JsonObject & command) {
     QString type = command["type"].toString();
     if (type == "modal") {
         active = QApplication::activeModalWidget();
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
         if (!active) {
             active = QApplication::modalWindow();
         }
-#endif
     } else if (type == "popup") {
         active = QApplication::activePopupWidget();
     } else if (type == "focus") {
         active = QApplication::focusWidget();
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
         if (!active) {
             active = QApplication::focusWindow();
         }
-#endif
     } else {
         active = QApplication::activeWindow();
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
         if (!active) {
             QWindowList lst = QGuiApplication::topLevelWindows();
             if (!lst.isEmpty()) {
                 active = lst.first();
             }
         }
-#endif
     }
     if (!active) {
         return createError(
@@ -481,13 +477,11 @@ QtJson::JsonObject Player::widgets_list(const QtJson::JsonObject & command) {
         } else {
             // no qwidgets, this is probably a qtquick app - anyway, check for
             // windows
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
             foreach (QWindow * window, QApplication::topLevelWindows()) {
                 QtJson::JsonObject resultWindow;
                 dump_object(window, resultWindow, with_properties);
                 result[resultWindow["path"].toString()] = resultWindow;
             }
-#endif
         }
     }
     return result;
@@ -495,7 +489,7 @@ QtJson::JsonObject Player::widgets_list(const QtJson::JsonObject & command) {
 
 QtJson::JsonObject Player::quit(const QtJson::JsonObject &) {
     if (qApp) {
-        qApp->quit();
+        qApp->exit();
     }
     QtJson::JsonObject result;
     return result;
@@ -541,6 +535,10 @@ QtJson::JsonObject Player::widget_click(const QtJson::JsonObject & command) {
 QtJson::JsonObject Player::quick_item_click(
     const QtJson::JsonObject & command) {
 #ifdef QT_QUICK_LIB
+#if QT_VERSION_MAJOR >= 6
+    return createError(
+        "Qt5Only", "This method is currently not supported with Qt6.");
+#endif
     QuickItemLocatorContext ctx(this, command, "oid");
     if (ctx.hasError()) {
         return ctx.lastError;
@@ -847,10 +845,20 @@ QtJson::JsonObject Player::grab(const QtJson::JsonObject & command) {
         if (ctx.hasError()) {
             return ctx.lastError;
         }
+#if QT_VERSION_MAJOR >= 6
+        pixmap = ctx.widget->grab();
+#else
         pixmap = QPixmap::grabWidget(ctx.widget);
+#endif
     } else {
         // grab the whole screen
+#if QT_VERSION_MAJOR >= 6
+        if (QScreen* screen = QGuiApplication::primaryScreen()) {
+            pixmap = screen->grabWindow();
+        }
+#else
         pixmap = QPixmap::grabWindow(QApplication::desktop()->winId());
+#endif
     }
     QString format = command["format"].toString();
     if (format.isEmpty()) {
diff --git a/server/protocole/protocole.cpp b/server/libFunq/protocole.cpp
similarity index 98%
rename from server/protocole/protocole.cpp
rename to server/libFunq/protocole.cpp
index 3557f68..4373673 100644
--- a/server/protocole/protocole.cpp
+++ b/server/libFunq/protocole.cpp
@@ -92,7 +92,7 @@ bool Protocole::sendMessage(const QByteArray & ba) {
         return false;
     }
     QByteArray messageToSend;
-    messageToSend.append(QString::number(ba.size()));
+    messageToSend.append(QString::number(ba.size()).toUtf8());
     messageToSend.append('\n');
     messageToSend.append(ba);
     m_device->write(messageToSend);
diff --git a/server/protocole/protocole.h b/server/libFunq/protocole.h
similarity index 100%
rename from server/protocole/protocole.h
rename to server/libFunq/protocole.h
diff --git a/server/protocole/protocole.pri b/server/protocole/protocole.pri
deleted file mode 100644
index 427319f..0000000
--- a/server/protocole/protocole.pri
+++ /dev/null
@@ -1,15 +0,0 @@
-
-QT       += core network
-
-INCLUDEPATH += $${PWD}
-
-SOURCES += $${PWD}/protocole.cpp \
-    $${PWD}/json.cpp \
-    $${PWD}/jsonclient.cpp \
-    $${PWD}/delayedresponse.cpp
-
-HEADERS += \
-    $${PWD}/protocole.h \
-    $${PWD}/json.h \
-    $${PWD}/jsonclient.h \
-    $${PWD}/delayedresponse.h
diff --git a/server/setup.py b/server/setup.py
index cbfd494..e927dd6 100644
--- a/server/setup.py
+++ b/server/setup.py
@@ -13,20 +13,14 @@
 IS_WINDOWS = platform.system() == 'Windows'
 IS_MAC = platform.system() == 'Darwin'
 
-if sys.version_info < (2, 7):
-    sys.exit("Python version must be > 2.7")
-elif sys.version_info > (3,) and IS_WINDOWS:
-    sys.exit('funq server under windows require winappdbg'
-             ' which is not available under python 3 currenly.')
-
-install_requires = []
-if IS_WINDOWS:
-    install_requires.append('winappdbg')
+if sys.version_info < (3, 5):
+    sys.exit("Python version must be >= 3.5")
 
 
 def read(*paths):
     this_dir = os.path.dirname(os.path.realpath(__file__))
-    return open(os.path.join(this_dir, *paths)).read()
+    content = open(os.path.join(this_dir, *paths), "rb").read()
+    return content.decode("utf-8")
 
 
 version = re.search("__version__ = '(.+)'", read('funq_server/__init__.py')).group(1)
@@ -39,11 +33,10 @@ class build_libfunq(Command):
     user_options = [
         ('build-lib=', 'd', "directory to \"build\" (copy) to"),
         ('force', 'f', "forcibly build everything (ignore file timestamps)"),
-        ('qmake-path=', None, "path to the qmake executable"),
+        ('cmake-path=', None, "path to the cmake executable"),
         ('make-path=', None, "path to the make executable"),
         ('debug', 'g',
          "compile/link with debugging information"),
-        ('cxxflags=', None, "custom QMAKE_CXXFLAGS passed to qmake"),
         ('inplace', 'i',
          "ignore build-lib and put compiled extensions into the source " +
          "directory alongside your pure Python modules"),
@@ -56,12 +49,12 @@ def initialize_options(self):
         self.force = None
         self.inplace = None
         self.debug = None
-        self.cxxflags = None
-        self.qmake_path = None
+        self.cmake_path = None
         self.make_path = None
+        self.qt_version = None
 
         if IS_WINDOWS:
-            self.funqlib_name = 'Funq.dll'
+            self.funqlib_name = 'libFunq.dll'
         elif IS_MAC:
             self.funqlib_name = 'libFunq.dylib'
         else:
@@ -72,12 +65,12 @@ def finalize_options(self):
                                    ('force', 'force'),
                                    ('debug', 'debug'),
                                    ('build_lib', 'build_lib'))
-        if self.qmake_path is None:
-            self.qmake_path = os.environ.get('FUNQ_QMAKE_PATH') or 'qmake'
+        if self.cmake_path is None:
+            self.cmake_path = os.environ.get('FUNQ_CMAKE_PATH') or 'cmake'
         if self.make_path is None:
             self.make_path = os.environ.get('FUNQ_MAKE_PATH') or 'make'
-        if self.cxxflags is None:
-            self.cxxflags = os.environ.get('FUNQ_CXXFLAGS')
+        if self.qt_version is None:
+            self.qt_version = os.environ.get('FUNQ_QT_MAJOR_VERSION')
 
     def funqlib_out_path(self):
         funqlib_base_dir = '.' if self.inplace else self.build_lib
@@ -86,15 +79,17 @@ def funqlib_out_path(self):
     def run(self):
         if self.force:
             subprocess.call([self.make_path, 'clean'], shell=True)
-        qmake_cmd = [self.qmake_path, '-r']
-        if IS_WINDOWS:
-            qmake_cmd += ['-spec', 'win32-g++']
-        if self.cxxflags:
-            qmake_cmd += ['QMAKE_CXXFLAGS="' + self.cxxflags + '"']
-        print('running %s' % qmake_cmd)
-        subprocess.check_call(qmake_cmd)
-        buildtype = 'debug' if self.debug else 'release'
-        make_cmd = [self.make_path, buildtype]
+        buildtype = 'Debug' if self.debug else 'Release'
+        cmake_cmd = [
+            self.cmake_path, '.',
+            '-DCMAKE_BUILD_TYPE={}'.format(buildtype),
+        ]
+        if self.qt_version is not None:
+            cmake_cmd += ['-DQT_MAJOR_VERSION={}'.format(self.qt_version)]
+        print('running %s' % cmake_cmd)
+        subprocess.check_call(cmake_cmd)
+
+        make_cmd = [self.make_path]
         print('running %s' % make_cmd)
         subprocess.check_call(make_cmd, shell=True)
 
@@ -102,7 +97,7 @@ def run(self):
         lib_dir = os.path.dirname(lib_path)
         if not os.path.isdir(lib_dir):
             os.makedirs(lib_dir)
-        shutil.copy2(os.path.join('bin', self.funqlib_name), lib_path)
+        shutil.copy2(os.path.join('libFunq', self.funqlib_name), lib_path)
 
     def get_outputs(self):
         return [self.funqlib_out_path()]
@@ -145,6 +140,6 @@ def run(self):
         'install': install,
         'develop': develop,
     },
-    install_requires=install_requires,
+    install_requires=[],
     license='CeCILL v1.2',
 )


Test patch:
diff --git a/client/doc/tutorial_test_widgets.py b/client/doc/tutorial_test_widgets.py
index c3b8177..b5316f3 100644
--- a/client/doc/tutorial_test_widgets.py
+++ b/client/doc/tutorial_test_widgets.py
@@ -22,7 +22,7 @@ def test_libelle_btn_test(self):
         btn_test = self.funq.widget('btnTest')
         properties = btn_test.properties()
 
-        self.assertEquals(properties['text'], 'Test')
+        self.assertEqual(properties['text'], 'Test')
 
     def test_open_dialog(self):
         """
@@ -31,7 +31,7 @@ def test_open_dialog(self):
         self.funq.widget('btnTest').click()
         dlg_label = self.funq.widget('dialog1_label')
 
-        self.assertEquals(dlg_label.properties()['text'], "Button clicked")
+        self.assertEqual(dlg_label.properties()['text'], "Button clicked")
 
     def test_tableview_content(self):
         """
@@ -39,12 +39,12 @@ def test_tableview_content(self):
         """
         view = self.funq.widget('tableview')
         items = list(view.model_items().iter())
-        self.assertEquals(len(items), 16)
+        self.assertEqual(len(items), 16)
 
         for item in items:
             text = "row {r}, column {c}".format(r=item.row,
                                                 c=item.column)
-            self.assertEquals(item.value, text)
+            self.assertEqual(item.value, text)
 
     def test_some_treeview_content(self):
         """
@@ -52,8 +52,8 @@ def test_some_treeview_content(self):
         """
         model = self.funq.widget('treeview').model_items()
 
-        item = model.item_by_named_path([u"item 1", u"item 1-2"])
-        parent_item = model.item_by_named_path([u"item 1"])
+        item = model.item_by_named_path(["item 1", "item 1-2"])
+        parent_item = model.item_by_named_path(["item 1"])
 
-        self.assertEquals(item.value, u"item 1-2")
+        self.assertEqual(item.value, "item 1-2")
         self.assertIn(item, parent_item.items)
diff --git a/client/funq/testcase.py b/client/funq/testcase.py
index 6b686fb..6c2c374 100644
--- a/client/funq/testcase.py
+++ b/client/funq/testcase.py
@@ -45,14 +45,6 @@
 import inspect
 
 
-# python 3 compatibility
-# https://stackoverflow.com/questions/11301138/how-to-check-if-variable-is-string-with-python-2-and-3-compatibility)
-try:
-    unicode
-except NameError:
-    unicode = str
-
-
 class AssertionSuccessError(AssertionError):
 
     """
@@ -67,7 +59,7 @@ def __init__(self, name):
         self.name = name
 
     def __str__(self):
-        return u"Test %s passed but it is decorated as TODO" % self.name
+        return "Test %s passed but it is decorated as TODO" % self.name
 
     def __rep__(self):
         return self.__str__()
@@ -100,8 +92,8 @@ def wrapper(*args, **kwargs):
             try:
                 func(*args, **kwargs)
             except exception_cls as err:
-                err = u"%s" % err
-                if isinstance(err, unicode):
+                err = "%s" % err
+                if isinstance(err, str):
                     err = err.encode(
                         'utf-8', errors='ignore')  # pylint: disable=E1103
                 skip_msg = skip_message.encode('utf-8', errors='ignore')
@@ -202,7 +194,8 @@ class MetaParameterized(type):
     RE_ESCAPE_BAD_CHARS = re.compile(r'[\.\(\) -/]')
 
     def __new__(cls, name, bases, attrs):
-        for k, v in attrs.items():
+        for k in list(attrs.keys()):
+            v = attrs[k]
             if callable(v) and hasattr(v, 'parameters'):
                 for func_suffix, args, kwargs in v.parameters:
                     func_suffix = cls.RE_ESCAPE_BAD_CHARS.sub('_', func_suffix)
@@ -246,7 +239,7 @@ def register_funq_app_registry(registry):
     BaseTestCase.__app_registry__ = registry
 
 
-class BaseTestCase(unittest.TestCase):
+class BaseTestCase(unittest.TestCase, metaclass=MetaParameterized):
 
     """
     Abstract class of a testcase for Funq.
@@ -255,9 +248,8 @@ class BaseTestCase(unittest.TestCase):
     :class:`MetaParameterized` that allows to generate methods from data.
 
     It inherits from :class:`unittest.TestCase`, thus allowing to use very
-    useful methods like assertEquals, assertFalse, etc.
+    useful methods like assertEqual, assertFalse, etc.
     """
-    __metaclass__ = MetaParameterized
     __app_registry__ = None
 
     longMessage = True
@@ -279,7 +271,7 @@ def __delete_funq_ctx(self):
     def id(self):
         cls = self.__class__
         fname = inspect.getsourcefile(cls)[len(os.getcwd()) + 1:]
-        return u"%s:%s.%s" % (fname, cls.__name__, self._testMethodName)
+        return "%s:%s.%s" % (fname, cls.__name__, self._testMethodName)
 
 
 class FunqTestCase(BaseTestCase):
@@ -336,7 +328,7 @@ def __app_config__(cls):  # pylint: disable=E0213
     def _create_funq_ctx(self):
         ctx = {}
         self.funq = {}
-        for k, v in self.__app_config__.iteritems():
+        for k, v in self.__app_config__.items():
             ctx[k] = ApplicationContext(v)
             self.funq[k] = weakref.proxy(ctx[k].funq)
         return ctx
diff --git a/client/funq/tests/test_aliases.py b/client/funq/tests/test_aliases.py
index 78b3a0a..6ce9772 100644
--- a/client/funq/tests/test_aliases.py
+++ b/client/funq/tests/test_aliases.py
@@ -33,7 +33,7 @@
 # knowledge of the CeCILL v2.1 license and that you accept its terms.
 
 from nose.tools import assert_equals, raises
-from funq.aliases import HooqAliases, HooqAliasesInvalidLineError,\
+from funq.aliases import HooqAliases, HooqAliasesInvalidLineError, \
     HooqAliasesKeyError
 from tempfile import NamedTemporaryFile
 import os
diff --git a/client/funq/tests/test_client.py b/client/funq/tests/test_client.py
index 106c78b..b527f98 100644
--- a/client/funq/tests/test_client.py
+++ b/client/funq/tests/test_client.py
@@ -37,7 +37,7 @@
 import os
 import subprocess
 
-from ConfigParser import ConfigParser, NoOptionError
+from configparser import ConfigParser, NoOptionError
 
 
 class ApplicationConfig(client.ApplicationConfig):
@@ -47,7 +47,7 @@ class ApplicationConfig(client.ApplicationConfig):
 class GlobalOptions(object):
 
     def __init__(self, **kwds):
-        for k, v in kwds.iteritems():
+        for k, v in kwds.items():
             setattr(self, k, v)
 
 
diff --git a/client/funq/tests/test_screenshoter.py b/client/funq/tests/test_screenshoter.py
index f31f2d5..e09253c 100644
--- a/client/funq/tests/test_screenshoter.py
+++ b/client/funq/tests/test_screenshoter.py
@@ -64,7 +64,7 @@ def test_take_one_screenshot():
     funq = FakeFunqClient()
     with ScreenShoterCtx() as ctx:
         ctx.take_screenshot(funq, "hello")
-        assert_equals(map(os.path.basename, funq.screens), ["0.png"])
+        assert_equals(list(map(os.path.basename, funq.screens)), ["0.png"])
         assert_true("0.png: hello" in open(
             os.path.join(ctx.working_folder, 'images.txt')).read())
 
@@ -76,7 +76,8 @@ def test_take_screenshots():
 
         ctx.take_screenshot(funq, "thisisit")
 
-        assert_equals(map(os.path.basename, funq.screens), ["0.png", "1.png"])
+        assert_equals(list(map(os.path.basename, funq.screens)),
+                      ["0.png", "1.png"])
         content = open(os.path.join(ctx.working_folder, 'images.txt')).read()
         assert_true("0.png: hello" in content)
         assert_true("1.png: thisisit" in content)
diff --git a/server/player_tester/CMakeLists.txt b/server/player_tester/CMakeLists.txt
new file mode 100644
index 0000000..b469ff1
--- /dev/null
+++ b/server/player_tester/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTORCC OFF)
+
+add_executable(
+  player_tester
+  WIN32
+  fenPrincipale.cpp
+  fenPrincipale.h
+  fenPrincipale.ui
+  main.cpp
+)
+target_link_libraries(
+  player_tester PUBLIC
+  ${QT}::Core
+  ${QT}::Gui
+  ${QT}::Widgets
+)
diff --git a/server/player_tester/player_tester.pro b/server/player_tester/player_tester.pro
deleted file mode 100755
index 8ba2485..0000000
--- a/server/player_tester/player_tester.pro
+++ /dev/null
@@ -1,39 +0,0 @@
-#-------------------------------------------------
-#
-# Project created by QtCreator 2013-12-13T07:25:27
-#
-#-------------------------------------------------
-
-QT       += core gui
-
-greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
-
-TARGET = player_tester
-TEMPLATE = app
-
-DEPENDPATH += .
-INCLUDEPATH += .
-
-
-SOURCES += main.cpp\
-    fenPrincipale.cpp
-
-HEADERS  += \
-    fenPrincipale.h
-
-FORMS    += \
-    fenPrincipale.ui
-
-win32 {
-        #SOURCES += WinDll.cpp
-        target.path = $${OUT_PWD}/../funq-release
-        CONFIG(debug, debug|release) {
-                target.path = $${OUT_PWD}/../funq-debug
-        }
-        INSTALLS += target
-}
-
-unix {
-        target.path = /${DESTDIR}$$BINDIR
-        INSTALLS += target
-}
diff --git a/server/run_tests_lcov.sh b/server/run_tests_lcov.sh
deleted file mode 100755
index 85f298f..0000000
--- a/server/run_tests_lcov.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-rm -f `find tests -name '*.gcno' -o -name '*.gcda'`
-rm -f `find tests -name Makefile`
-rm -Rf test-lcov-html
-rm -f .coverage.*
-
-qmake tests/unittests.pro CONFIG+=debug COVERAGE=1 -r
-
-make -C tests clean
-make -C tests -j4
-
-lcov -c -i -d . -o .coverage.base
-
-make -C tests check
-
-lcov -c -d . -o .coverage.run
-
-lcov -d . -a .coverage.base -a .coverage.run -o .coverage.total
-
-lcov -r .coverage.total '/usr/*' 'tests/*' -o .coverage.usefull
-
-genhtml --no-branch-coverage -o test-lcov-html .coverage.usefull
diff --git a/server/tests/common.pri b/server/tests/common.pri
deleted file mode 100644
index 5e0507e..0000000
--- a/server/tests/common.pri
+++ /dev/null
@@ -1,16 +0,0 @@
-# compile avec les flags pour le coverage
-# fonctionne avec clang / gcc sous unix
-#
-# utilisation: qmake CONFIG+=debug COVERAGE=1
-unix {
-    contains(COVERAGE, 1) {
-        QMAKE_CXXFLAGS_DEBUG += --coverage
-        QMAKE_LFLAGS_DEBUG += --coverage
-    }
-}
-
-greaterThan(QT_MAJOR_VERSION, 4): QT += widgets quick
-
-# Disable deprecation warnings since we want to be backward compatible to Qt4
-# and thus have to use some methods which are deprecated in recent Qt versions.
-DEFINES += QT_NO_DEPRECATED_WARNINGS=1
diff --git a/server/tests/libFunq/CMakeLists.txt b/server/tests/libFunq/CMakeLists.txt
new file mode 100644
index 0000000..5fd769a
--- /dev/null
+++ b/server/tests/libFunq/CMakeLists.txt
@@ -0,0 +1,25 @@
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTORCC OFF)
+
+add_executable(
+  testLibFunq
+  WIN32
+  test.cpp
+)
+target_include_directories(
+  testLibFunq PUBLIC
+  "${CMAKE_SOURCE_DIR}/libFunq"
+)
+target_compile_options(
+  testLibFunq PUBLIC
+  "-DSOURCE_DIR=\"${CMAKE_SOURCE_DIR}/tests/libFunq/\""
+)
+target_link_libraries(
+  testLibFunq PUBLIC
+  FunqStatic
+  ${QT}::Core
+  ${QT}::Gui
+  ${QT}::Widgets
+  $<$<BOOL:${WITH_QTQUICK}>:${QT}::Quick>
+)
diff --git a/server/tests/libFunq/libFunq.pro b/server/tests/libFunq/libFunq.pro
deleted file mode 100644
index 7709295..0000000
--- a/server/tests/libFunq/libFunq.pro
+++ /dev/null
@@ -1,13 +0,0 @@
-TARGET = testLibFunq
-
-TEMPLATE = app
-CONFIG += testcase qtestlib
-
-INCLUDEPATH += ../../libFunq
-
-DEFINES += SOURCE_DIR=\\\"$${PWD}/\\\"
-
-SOURCES = test.cpp
-
-include(../common.pri)
-include(../../libFunq/libFunq.pri)
diff --git a/server/tests/libFunq/test.cpp b/server/tests/libFunq/test.cpp
index bd53e73..faf7045 100644
--- a/server/tests/libFunq/test.cpp
+++ b/server/tests/libFunq/test.cpp
@@ -906,6 +906,7 @@ private slots:
         QCOMPARE(result["errName"].toString(), QString("InvalidQuickItem"));
     }
 
+#if QT_VERSION_MAJOR < 6
     void test_quick_item_click() {
         QQuickView view;
         view.setSource(QUrl::fromLocalFile(SOURCE_DIR "test_click.qml"));
@@ -938,6 +939,7 @@ private slots:
         QCOMPARE(item->property("color").toString(), QString("#ffffff"));
     }
 #endif
+#endif
 };
 
 QTEST_MAIN(LibFunqTest)
diff --git a/server/tests/protocole/CMakeLists.txt b/server/tests/protocole/CMakeLists.txt
new file mode 100644
index 0000000..3966cd8
--- /dev/null
+++ b/server/tests/protocole/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC OFF)
+set(CMAKE_AUTORCC OFF)
+
+add_executable(
+  testProtocole
+  WIN32
+  test.cpp
+)
+target_include_directories(
+  testProtocole PUBLIC
+  "${CMAKE_SOURCE_DIR}/libFunq"
+)
+target_link_libraries(
+  testProtocole PUBLIC
+  FunqStatic
+  ${QT}::Core
+)
diff --git a/server/tests/protocole/protocole.pro b/server/tests/protocole/protocole.pro
deleted file mode 100644
index 41c3194..0000000
--- a/server/tests/protocole/protocole.pro
+++ /dev/null
@@ -1,9 +0,0 @@
-TARGET = testProtocole
-
-TEMPLATE = app
-CONFIG += testcase qtestlib
-
-SOURCES = test.cpp
-
-include(../common.pri)
-include(../../protocole/protocole.pri)
diff --git a/server/tests/unittests.pro b/server/tests/unittests.pro
deleted file mode 100644
index 6e5d495..0000000
--- a/server/tests/unittests.pro
+++ /dev/null
@@ -1,3 +0,0 @@
-TEMPLATE = subdirs
-
-SUBDIRS = protocole libFunq
diff --git a/tests-functionnal/funq-test-app/CMakeLists.txt b/tests-functionnal/funq-test-app/CMakeLists.txt
new file mode 100644
index 0000000..63a4f9c
--- /dev/null
+++ b/tests-functionnal/funq-test-app/CMakeLists.txt
@@ -0,0 +1,94 @@
+# CMake version configuration
+cmake_minimum_required(VERSION 3.5...3.19)
+if(${CMAKE_VERSION} VERSION_LESS 3.12)
+  cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
+endif()
+
+# Define project
+project(funq-test-app LANGUAGES CXX)
+
+# Global options
+option(BUILD_DISALLOW_WARNINGS
+       "Disallow compiler warnings during build (build with -Werror)." OFF
+)
+
+# Create a release build by default
+# (Code based on https://blog.kitware.com/cmake-and-the-default-build-type/)
+set(default_build_type "Release")
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  # Set the build type
+  message(
+    STATUS
+      "Setting build type to '${default_build_type}' as none was specified."
+  )
+  set(CMAKE_BUILD_TYPE
+      "${default_build_type}"
+      CACHE STRING "Choose the type of build." FORCE
+  )
+
+  # Set the possible values of build type for cmake-gui
+  set_property(
+    CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel"
+                                    "RelWithDebInfo"
+  )
+endif()
+
+# Auto-include current directory
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+# If Qt version is not explicitly specified, automatically determine it
+if(NOT QT_MAJOR_VERSION)
+  find_package(Qt NAMES Qt5 Qt6 REQUIRED COMPONENTS Core)
+  set(QT_MAJOR_VERSION ${Qt_VERSION_MAJOR})
+endif()
+set(QT "Qt${QT_MAJOR_VERSION}")
+
+# Use C++11 (Qt5) or C++17 (Qt6)
+if(QT_MAJOR_VERSION EQUAL 6)
+  set(CMAKE_CXX_STANDARD 17)
+else()
+  set(CMAKE_CXX_STANDARD 11)
+endif()
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+# Set common compile options
+add_compile_options(-Wall -Wextra -Wpedantic)
+if(BUILD_DISALLOW_WARNINGS)
+  message(STATUS "Building with -Werror")
+  add_compile_options(-Werror)
+endif()
+if(WIN32)
+  add_definitions(-DUNICODE -D_UNICODE)
+endif()
+
+# Disable Qt functions which are deprecated in Qt <= 5.0 to enforce us
+# migrating away from them. Always set this to the lowest Qt version we want
+# to support.
+add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050000)
+
+# Not sure what we should do with deprecation warnings. Better keeping
+# backwards compatibility with already existing & supported environments
+# instead of forward compatibility with not yet existing environments, no?
+add_definitions(-DQT_NO_DEPRECATED_WARNINGS)
+
+# Find Qt
+find_package(${QT} REQUIRED COMPONENTS Core Gui Network Quick Widgets Test)
+message(STATUS "Building with Qt ${${QT}_VERSION}")
+
+# Build application
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC OFF)
+set(CMAKE_AUTORCC OFF)
+add_executable(
+  funq-test-app
+  WIN32
+  main.cpp
+  widgets.h
+)
+target_link_libraries(
+  funq-test-app PUBLIC
+  ${QT}::Core
+  ${QT}::Gui
+  ${QT}::Widgets
+)
diff --git a/tests-functionnal/funq-test-app/funq-test-app.pro b/tests-functionnal/funq-test-app/funq-test-app.pro
deleted file mode 100644
index 0f432a2..0000000
--- a/tests-functionnal/funq-test-app/funq-test-app.pro
+++ /dev/null
@@ -1,13 +0,0 @@
-TEMPLATE = app
-TARGET = funq-test-app
-DESTDIR = ./
-
-greaterThan(QT_MAJOR_VERSION, 4): QT += widgets quick
-
-CONFIG -= app_bundle
-
-SOURCES += \
-    main.cpp \
-
-HEADERS += \
-    widgets.h \
diff --git a/tests-functionnal/funq-test-app/main.cpp b/tests-functionnal/funq-test-app/main.cpp
index 8fd5f4f..dd21fc7 100644
--- a/tests-functionnal/funq-test-app/main.cpp
+++ b/tests-functionnal/funq-test-app/main.cpp
@@ -35,6 +35,7 @@ knowledge of the CeCILL v2.1 license and that you accept its terms.
 #include "widgets.h"
 
 #include <QApplication>
+#include <QThread>
 
 template <typename T>
 inline void execDialog(QLabel * statusLabel, QWidget * parent) {
@@ -45,6 +46,13 @@ inline void execDialog(QLabel * statusLabel, QWidget * parent) {
 int main(int argc, char * argv[]) {
     QApplication app(argc, argv);
 
+    if (app.arguments().contains("--exit-after-startup")) {
+        // Used to test injection without user interaction.
+        qDebug() << "Application started with '--exit-after-startup'.";
+        QThread::msleep(5000);
+        return 0;
+    }
+
     if (app.arguments().contains("--show-message-box-at-startup")) {
         // This is needed to test if the injection of libFunq also works if Qt's
         // main event loop is not called directly at application startup. A
diff --git a/tests-functionnal/funq-test-app/widgets.h b/tests-functionnal/funq-test-app/widgets.h
index fc95229..e89b0f1 100644
--- a/tests-functionnal/funq-test-app/widgets.h
+++ b/tests-functionnal/funq-test-app/widgets.h
@@ -33,11 +33,7 @@ knowledge of the CeCILL v2.1 license and that you accept its terms.
 */
 
 #include <QtCore>
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
 #include <QtWidgets>
-#else
-#include <QtGui>
-#endif
 
 class SimpleDialog : public QDialog {
     Q_OBJECT
@@ -232,13 +228,13 @@ class ComboBoxDialog : public SimpleDialog {
         for (int i = 0; i < 10; ++i) {
             combobox->addItem(QString("Item %1").arg(i));
         }
-        connect(combobox, SIGNAL(currentIndexChanged(QString)), this,
-                SLOT(currentIndexChanged(QString)));
+        connect(combobox, SIGNAL(currentTextChanged(QString)), this,
+                SLOT(currentTextChanged(QString)));
         layout()->addWidget(combobox);
     }
 
 private slots:
-    void currentIndexChanged(const QString & text) {
+    void currentTextChanged(const QString & text) {
         showResult("Text: " + text);
     }
 };
diff --git a/tests-functionnal/test_action.py b/tests-functionnal/test_action.py
index d40d452..ac17dc0 100644
--- a/tests-functionnal/test_action.py
+++ b/tests-functionnal/test_action.py
@@ -41,7 +41,7 @@ def test_blocking_trigger_to_nonblocking_action(self):
         self.start_dialog('action')
         action = self.funq.action(path='mainWindow::ActionDialog::nonblockingAction')
         action.trigger(blocking=True)
-        self.assertEquals(self.get_status_text(), 'nonblocking triggered !')
+        self.assertEqual(self.get_status_text(), 'nonblocking triggered !')
 
     def test_nonblocking_trigger_to_blocking_action(self):
         self.start_dialog('action')
@@ -51,4 +51,4 @@ def test_nonblocking_trigger_to_blocking_action(self):
         btn = self.funq.widget(path='QMessageBox::qt_msgbox_buttonbox::QPushButton')
         btn.click()
         # now the status text must be updated
-        self.assertEquals(self.get_status_text(), 'blocking triggered !')
+        self.assertEqual(self.get_status_text(), 'blocking triggered !')
diff --git a/tests-functionnal/test_click.py b/tests-functionnal/test_click.py
index 06baf14..fc8f254 100644
--- a/tests-functionnal/test_click.py
+++ b/tests-functionnal/test_click.py
@@ -42,25 +42,25 @@ def test_simple_click(self):
         self.start_dialog('click')
         btn = self.funq.widget(path='mainWindow::ClickDialog::QPushButton')
         btn.click()
-        self.assertEquals(self.get_status_text(), 'clicked !')
+        self.assertEqual(self.get_status_text(), 'clicked !')
 
     def test_right_click(self):
         self.start_dialog('widgetclick')
         btn = self.funq.widget(path='mainWindow::WidgetClickDialog')
         btn.click(btn='right')
-        self.assertEquals(self.get_status_text(), 'right clicked !')
+        self.assertEqual(self.get_status_text(), 'right clicked !')
 
     def test_middle_click(self):
         self.start_dialog('widgetclick')
         btn = self.funq.widget(path='mainWindow::WidgetClickDialog')
         btn.click(btn='middle')
-        self.assertEquals(self.get_status_text(), 'middle clicked !')
+        self.assertEqual(self.get_status_text(), 'middle clicked !')
 
     def test_double_click(self):
         self.start_dialog('doubleclick')
         btn = self.funq.widget(path='mainWindow::DoubleClickDialog')
         btn.dclick()
-        self.assertEquals(self.get_status_text(), 'double clicked !')
+        self.assertEqual(self.get_status_text(), 'double clicked !')
 
     @parameterized('sometext', 'Hello this is me !')
     @parameterized('someothertext', 'AAAA BBBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCC')
@@ -68,7 +68,7 @@ def test_key_click(self, text):
         self.start_dialog('keyclick')
         line = self.funq.widget(path='mainWindow::KeyClickDialog::QLineEdit')
         line.keyclick(text)
-        self.assertEquals(self.get_status_text(), text)
+        self.assertEqual(self.get_status_text(), text)
 
     @parameterized('R1', 'H', 0, 0)
     @parameterized('R1_by_name', 'H', 'C1', 0)
@@ -77,7 +77,7 @@ def test_click_header(self, orientation, index_or_name, result_index):
         self.start_dialog('table')
         header = self.funq.widget(path='mainWindow::TableDialog::QTableWidget::' + orientation)
         header.header_click(index_or_name)
-        self.assertEquals(
+        self.assertEqual(
             self.get_status_text(),
             orientation + " Header clicked: " + str(result_index)
         )
diff --git a/tests-functionnal/test_combobox.py b/tests-functionnal/test_combobox.py
index 82b9ade..d72e300 100644
--- a/tests-functionnal/test_combobox.py
+++ b/tests-functionnal/test_combobox.py
@@ -41,31 +41,31 @@ def test_set_current_text(self):
         self.start_dialog('combobox')
         cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox')
         cbx.set_current_text('Item 5')
-        self.assertEquals(self.get_status_text(), 'Text: Item 5')
+        self.assertEqual(self.get_status_text(), 'Text: Item 5')
 
     def test_model_items_count(self):
         self.start_dialog('combobox')
         cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox')
         items = cbx.model().items()
-        self.assertEquals(len(items.items), 10)
+        self.assertEqual(len(items.items), 10)
 
     def test_model_items_value(self):
         self.start_dialog('combobox')
         cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox')
         items = cbx.model().items()
         for i in range(10):
-            self.assertEquals(items.items[i].value, 'Item ' + str(i))
+            self.assertEqual(items.items[i].value, 'Item ' + str(i))
 
     def test_model_items_checkable(self):
         self.start_dialog('combobox')
         cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox')
         items = cbx.model().items()
         for i in range(10):
-            self.assertEquals(items.items[i].is_checkable(), False)
+            self.assertEqual(items.items[i].is_checkable(), False)
 
     def test_model_items_checked(self):
         self.start_dialog('combobox')
         cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox')
         items = cbx.model().items()
         for i in range(10):
-            self.assertEquals(items.items[i].is_checked(), False)
+            self.assertEqual(items.items[i].is_checked(), False)
diff --git a/tests-functionnal/test_injection.py b/tests-functionnal/test_injection.py
index f363cd2..3768300 100644
--- a/tests-functionnal/test_injection.py
+++ b/tests-functionnal/test_injection.py
@@ -60,4 +60,4 @@ def test_close_blocking_msgbox_at_startup(self):
         btn.click()
         # now the main window should be visible
         window = self.funq.widget(path='mainWindow::QWidget')
-        self.assertEquals(window.properties()['visible'], True)
+        self.assertEqual(window.properties()['visible'], True)
diff --git a/tests-functionnal/test_retrieve_widget.py b/tests-functionnal/test_retrieve_widget.py
index d8d7ae5..b75ba84 100644
--- a/tests-functionnal/test_retrieve_widget.py
+++ b/tests-functionnal/test_retrieve_widget.py
@@ -50,8 +50,8 @@ def test_widget(self, alias=None, path=None):
 
         self.assertIsInstance(lbl, Widget)
         self.assertIsInstance(lbl.client, FunqClient)
-        self.assertEquals(lbl.path, 'mainWindow::RetrieveWidget::QLabel')
-        self.assertEquals(lbl.classes, [u'QLabel', u'QFrame', u'QWidget', u'QObject'])
+        self.assertEqual(lbl.path, 'mainWindow::RetrieveWidget::QLabel')
+        self.assertEqual(lbl.classes, [u'QLabel', u'QFrame', u'QWidget', u'QObject'])
         self.assertTrue(lbl.oid)
 
     def test_widget_alias_unknow(self):
@@ -71,33 +71,33 @@ def test_widget_property(self):
         self.start_dialog('retrieve')
         lbl = self.funq.widget('lbl_retrieve')
 
-        self.assertEquals(lbl.properties()['text'], 'hello')
+        self.assertEqual(lbl.properties()['text'], 'hello')
 
     def test_widget_set_property(self):
         self.start_dialog('retrieve')
         lbl = self.funq.widget('lbl_retrieve')
         lbl.set_property('text', 'hello2')
-        self.assertEquals(lbl.properties()['text'], 'hello2')
+        self.assertEqual(lbl.properties()['text'], 'hello2')
 
     def test_widget_set_properties(self):
         self.start_dialog('retrieve')
         lbl = self.funq.widget('lbl_retrieve')
         lbl.set_properties(text='hello2', wordWrap=True)
-        self.assertEquals(lbl.properties()['text'], 'hello2')
-        self.assertEquals(lbl.properties()['wordWrap'], True)
+        self.assertEqual(lbl.properties()['text'], 'hello2')
+        self.assertEqual(lbl.properties()['wordWrap'], True)
 
     def test_widget_active(self):
         self.start_dialog('retrieve')
         self.funq.widget(path='mainWindow::RetrieveWidget')  # wait for the dialog to be shown
         active = self.funq.active_widget('modal')
-        self.assertEquals(active.path, 'mainWindow::RetrieveWidget')
+        self.assertEqual(active.path, 'mainWindow::RetrieveWidget')
 
     @parameterized('V', 'V', ['R1', 'R2'])
     @parameterized('H', 'H', ['C1', 'C2', 'C3'])
     def test_headertexts(self, orientation, texts):
         self.start_dialog('table')
         header = self.funq.widget(path='mainWindow::TableDialog::QTableWidget::' + orientation)
-        self.assertEquals(header.header_texts(), texts)
+        self.assertEqual(header.header_texts(), texts)
 
     def test_header_from_table(self):
         self.start_dialog('table')
diff --git a/tests-functionnal/test_shortcut.py b/tests-functionnal/test_shortcut.py
index bbac1dc..6d4dce9 100644
--- a/tests-functionnal/test_shortcut.py
+++ b/tests-functionnal/test_shortcut.py
@@ -47,7 +47,7 @@ def test_shortcut(self, sequence):
         dlg = self.funq.active_widget('modal')
         dlg.shortcut(sequence)
 
-        self.assertEquals(self.get_status_text(), "Shortcut: " + sequence)
+        self.assertEqual(self.get_status_text(), "Shortcut: " + sequence)
 
     @parameterized("CTRL+C,F2", ["CTRL+C", "F2"])
     @parameterized("CTRL+C,F2,ENTER", ["CTRL+C", "F2", "ENTER"])
@@ -58,10 +58,10 @@ def test_multi_shortcut(self, sequences):
         for sequence in sequences:
             dlg.shortcut(sequence)
 
-            self.assertEquals(self.get_status_text(), "Shortcut: " + sequence)
+            self.assertEqual(self.get_status_text(), "Shortcut: " + sequence)
 
     def test_on_push_button(self):
         self.start_dialog('click')
         btn = self.funq.widget(path='mainWindow::ClickDialog::QPushButton')
         btn.shortcut("ENTER")
-        self.assertEquals(self.get_status_text(), 'clicked !')
+        self.assertEqual(self.get_status_text(), 'clicked !')
diff --git a/tests-functionnal/test_tableview.py b/tests-functionnal/test_tableview.py
index d140ef1..7d6b004 100644
--- a/tests-functionnal/test_tableview.py
+++ b/tests-functionnal/test_tableview.py
@@ -41,7 +41,7 @@ def test_model_items_count(self):
         self.start_dialog('table')
         table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget')
         items = table.model().items()
-        self.assertEquals(len(items.items), 6)
+        self.assertEqual(len(items.items), 6)
 
     def test_model_items_value(self):
         self.start_dialog('table')
@@ -49,21 +49,21 @@ def test_model_items_value(self):
         items = table.model().items()
         for item in items.iter():
             text = str(item.column) + '.' + str(item.row)
-            self.assertEquals(item.value, text)
+            self.assertEqual(item.value, text)
 
     def test_model_items_checkable(self):
         self.start_dialog('table')
         table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget')
         items = table.model().items()
         for item in items.iter():
-            self.assertEquals(item.is_checkable(), False)
+            self.assertEqual(item.is_checkable(), False)
 
     def test_model_items_checked(self):
         self.start_dialog('table')
         table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget')
         items = table.model().items()
         for item in items.iter():
-            self.assertEquals(item.is_checked(), False)
+            self.assertEqual(item.is_checked(), False)
 
     def test_model_item_select(self):
         self.start_dialog('table')
diff --git a/tests-functionnal/test_widget.py b/tests-functionnal/test_widget.py
index cba9bc8..8df23d9 100644
--- a/tests-functionnal/test_widget.py
+++ b/tests-functionnal/test_widget.py
@@ -43,64 +43,64 @@ def test_move_x(self):
         widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget')
         props_before = widget.properties()
         new_x, new_y = widget.move(x=5)
-        self.assertEquals(new_x, 5)
-        self.assertEquals(new_y, props_before['y'])
+        self.assertEqual(new_x, 5)
+        self.assertEqual(new_y, props_before['y'])
         props_after = widget.properties()
-        self.assertEquals(props_after['x'], new_x)
-        self.assertEquals(props_after['y'], new_y)
+        self.assertEqual(props_after['x'], new_x)
+        self.assertEqual(props_after['y'], new_y)
 
     def test_move_y(self):
         self.start_dialog('widgetclick')
         widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget')
         props_before = widget.properties()
         new_x, new_y = widget.move(y=6)
-        self.assertEquals(new_x, props_before['x'])
-        self.assertEquals(new_y, 6)
+        self.assertEqual(new_x, props_before['x'])
+        self.assertEqual(new_y, 6)
         props_after = widget.properties()
-        self.assertEquals(props_after['x'], new_x)
-        self.assertEquals(props_after['y'], new_y)
+        self.assertEqual(props_after['x'], new_x)
+        self.assertEqual(props_after['y'], new_y)
 
     def test_move_xy(self):
         self.start_dialog('widgetclick')
         widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget')
         new_x, new_y = widget.move(x=13, y=37)
-        self.assertEquals(new_x, 13)
-        self.assertEquals(new_y, 37)
+        self.assertEqual(new_x, 13)
+        self.assertEqual(new_y, 37)
         props_after = widget.properties()
-        self.assertEquals(props_after['x'], new_x)
-        self.assertEquals(props_after['y'], new_y)
+        self.assertEqual(props_after['x'], new_x)
+        self.assertEqual(props_after['y'], new_y)
 
     def test_resize_width(self):
         self.start_dialog('widgetclick')
         widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget')
         props_before = widget.properties()
         new_width, new_height = widget.resize(width=42)
-        self.assertEquals(new_width, 42)
-        self.assertEquals(new_height, props_before['height'])
+        self.assertEqual(new_width, 42)
+        self.assertEqual(new_height, props_before['height'])
         props_after = widget.properties()
-        self.assertEquals(props_after['width'], new_width)
-        self.assertEquals(props_after['height'], new_height)
+        self.assertEqual(props_after['width'], new_width)
+        self.assertEqual(props_after['height'], new_height)
 
     def test_resize_height(self):
         self.start_dialog('widgetclick')
         widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget')
         props_before = widget.properties()
         new_width, new_height = widget.resize(height=24)
-        self.assertEquals(new_width, props_before['width'])
-        self.assertEquals(new_height, 24)
+        self.assertEqual(new_width, props_before['width'])
+        self.assertEqual(new_height, 24)
         props_after = widget.properties()
-        self.assertEquals(props_after['width'], new_width)
-        self.assertEquals(props_after['height'], new_height)
+        self.assertEqual(props_after['width'], new_width)
+        self.assertEqual(props_after['height'], new_height)
 
     def test_resize_both(self):
         self.start_dialog('widgetclick')
         widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget')
         new_width, new_height = widget.resize(width=13, height=37)
-        self.assertEquals(new_width, 13)
-        self.assertEquals(new_height, 37)
+        self.assertEqual(new_width, 13)
+        self.assertEqual(new_height, 37)
         props_after = widget.properties()
-        self.assertEquals(props_after['width'], new_width)
-        self.assertEquals(props_after['height'], new_height)
+        self.assertEqual(props_after['width'], new_width)
+        self.assertEqual(props_after['height'], new_height)
 
     def test_map_position_to_from_global(self):
         btn = self.funq.widget(path='mainWindow::QWidget::click')
@@ -108,7 +108,7 @@ def test_map_position_to_from_global(self):
         global_pos = btn.map_position_to(*local_pos, parent=None)
         self.assertNotEquals(global_pos, local_pos)
         local_pos_2 = btn.map_position_from(*global_pos, parent=None)
-        self.assertEquals(local_pos_2, local_pos)
+        self.assertEqual(local_pos_2, local_pos)
 
     def test_map_position_from_to_global(self):
         btn = self.funq.widget(path='mainWindow::QWidget::click')
@@ -116,7 +116,7 @@ def test_map_position_from_to_global(self):
         local_pos = btn.map_position_from(*global_pos, parent=None)
         self.assertNotEquals(local_pos, global_pos)
         global_pos_2 = btn.map_position_to(*local_pos, parent=None)
-        self.assertEquals(global_pos_2, global_pos)
+        self.assertEqual(global_pos_2, global_pos)
 
     def test_map_position_to_from_parent(self):
         win = self.funq.widget(path='mainWindow')
@@ -125,7 +125,7 @@ def test_map_position_to_from_parent(self):
         parent_pos = btn.map_position_to(*local_pos, parent=win)
         self.assertNotEquals(parent_pos, local_pos)
         local_pos_2 = btn.map_position_from(*parent_pos, parent=win)
-        self.assertEquals(local_pos_2, local_pos)
+        self.assertEqual(local_pos_2, local_pos)
 
     def test_map_position_from_to_parent(self):
         win = self.funq.widget(path='mainWindow')
@@ -134,4 +134,4 @@ def test_map_position_from_to_parent(self):
         local_pos = btn.map_position_from(*parent_pos, parent=win)
         self.assertNotEquals(local_pos, parent_pos)
         parent_pos_2 = btn.map_position_to(*local_pos, parent=win)
-        self.assertEquals(parent_pos_2, parent_pos)
+        self.assertEqual(parent_pos_2, parent_pos)