From 12617346d776af905ccfc2ec1a1d9f8011f8de57 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Tue, 2 Jan 2024 13:55:22 +0100 Subject: [PATCH] added very basic web replayer --- tools/web_replay/main.py | 21 +++++ tools/web_replay/ui/ReplayEngine.py | 121 ++++++++++++++++++++++++++++ tools/web_replay/ui/ReplayWindow.py | 18 +++++ tools/web_replay/ui/__init__.py | 2 + 4 files changed, 162 insertions(+) create mode 100644 tools/web_replay/main.py create mode 100644 tools/web_replay/ui/ReplayEngine.py create mode 100644 tools/web_replay/ui/ReplayWindow.py create mode 100644 tools/web_replay/ui/__init__.py diff --git a/tools/web_replay/main.py b/tools/web_replay/main.py new file mode 100644 index 0000000..a81d207 --- /dev/null +++ b/tools/web_replay/main.py @@ -0,0 +1,21 @@ +import sys + +from PyQt6.QtWidgets import QApplication + +from tools.web_replay.ui import ReplayWindow + + +if __name__ == "__main__": + # create the application + application = QApplication(sys.argv) + + from source.utils import compress + with open(r"C:\Users\RC606\Downloads\41a6268b-72e5-47a9-8106-6c15a0be366e.rsl", "rb") as file: + data = compress.uncompress_data(file.read()) + + # create the window + window = ReplayWindow(data["surveys"]["mission-gift-card"]["event"]) + window.show() + + # start the application + application.exec() diff --git a/tools/web_replay/ui/ReplayEngine.py b/tools/web_replay/ui/ReplayEngine.py new file mode 100644 index 0000000..267208b --- /dev/null +++ b/tools/web_replay/ui/ReplayEngine.py @@ -0,0 +1,121 @@ +from PyQt6.QtCore import Qt, QUrl, QSize, QPointF +from PyQt6.QtGui import QKeyEvent, QMouseEvent +from PyQt6.QtWebEngineWidgets import QWebEngineView +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QApplication + + +class ReplayEngine(QWidget): + """ + This widget allow to replay some event that occurred on a web page + """ + + def __init__(self, replay_data: dict): + super().__init__() + + self.replay_data = replay_data + self.iterator = iter(self.replay_data) + + # layout + self._layout = QVBoxLayout() + self.setLayout(self._layout) + + # cursor + self.cursor = QLabel(self) + self.cursor.setFixedSize(20, 20) + self.cursor.setStyleSheet("background-color: red; border-radius: 10px;") + + # web + self.web = QWebEngineView() + self._layout.addWidget(self.web) + + def run_event(self, event: dict): + match event["type"]: + case "success": + # success event + print(f"success ! ({event['time']}s)") + + case "url": + # changing url event + self.web.setUrl(QUrl(event["url"])) + + case "resize": + # changing widget size event + self.web.resize(QSize(*event["size"])) + + # TODO: better way ? + self.window().resize(QSize(*event["size"])) + + case "keyboard_press": + # keyboard key pressed event + key = QKeyEvent( + QKeyEvent.Type.KeyPress, + event["key"], + Qt.KeyboardModifier.NoModifier + ) + QApplication.sendEvent(self.web.page(), key) + + case "keyboard_release": + # keyboard key released event + key = QKeyEvent( + QKeyEvent.Type.KeyRelease, + event["key"], + Qt.KeyboardModifier.NoModifier + ) + QApplication.sendEvent(self.web.page(), key) + + case "mouse_press": + # mouse pressed event + key = QMouseEvent( + QMouseEvent.Type.KeyPress, + QPointF(*event["position"]), + Qt.MouseButton(event["button"]), + Qt.MouseButton.NoButton, + Qt.KeyboardModifier.NoModifier + ) + QApplication.sendEvent(self.web.page(), key) + + case "mouse_release": + # mouse pressed event + key = QMouseEvent( + QMouseEvent.Type.KeyRelease, + QPointF(*event["position"]), + Qt.MouseButton(event["button"]), + Qt.MouseButton.NoButton, + Qt.KeyboardModifier.NoModifier + ) + QApplication.sendEvent(self.web.page(), key) + + # NOTE: this event is redundant + # case "mouse_double_click": + # # mouse double-clicked event + # key = QMouseEvent(QMouseEvent.Type.MouseButtonDblClick, event["position"], event["button"]) + # QApplication.sendEvent(self.page(), key) + + case "mouse_move": + # mouse moved event + key = QMouseEvent( + QMouseEvent.Type.KeyRelease, + QPointF(*event["position"]), + Qt.MouseButton.NoButton, + Qt.MouseButton.NoButton, + Qt.KeyboardModifier.NoModifier + ) + QApplication.sendEvent(self.web.page(), key) + + # move the fake cursor + self.cursor.move(QPointF(*event["position"]).toPoint() - self.cursor.rect().center()) + self.cursor.raise_() + + case "scroll": + # scroll event + x, y = event["position"] + self.web.page().runJavaScript(f"window.scrollTo({x}, {y});") + + def next(self): + try: + event = next(self.iterator) + except StopIteration: + print("end of record") + return + + self.run_event(event) diff --git a/tools/web_replay/ui/ReplayWindow.py b/tools/web_replay/ui/ReplayWindow.py new file mode 100644 index 0000000..823004a --- /dev/null +++ b/tools/web_replay/ui/ReplayWindow.py @@ -0,0 +1,18 @@ +from PyQt6.QtCore import QTimer +from PyQt6.QtWidgets import QMainWindow + +from tools.web_replay.ui import ReplayEngine + + +class ReplayWindow(QMainWindow): + def __init__(self, replay_data: dict): + super().__init__() + + self.replay_engine = ReplayEngine(replay_data) + self.setCentralWidget(self.replay_engine) + + # TODO: TEST REMOVE + self.timer = QTimer() + self.timer.setInterval(10) + self.timer.timeout.connect(self.replay_engine.next) # NOQA: connect exist + self.timer.start() diff --git a/tools/web_replay/ui/__init__.py b/tools/web_replay/ui/__init__.py new file mode 100644 index 0000000..37f9be0 --- /dev/null +++ b/tools/web_replay/ui/__init__.py @@ -0,0 +1,2 @@ +from .ReplayEngine import ReplayEngine +from .ReplayWindow import ReplayWindow