Skip to content
Merged

Dev #127

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "je_api_testka_dev"
version = "0.0.126"
version = "0.0.128"
authors = [
{ name = "JE-Chen", email = "jechenmailman@gmail.com" },
]
Expand Down
60 changes: 34 additions & 26 deletions je_api_testka/gui/api_testka_gui_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,48 @@
from je_api_testka.httpx_wrapper.httpx_method import test_api_method_httpx


def _process_response(response_data: dict):
"""
Process API response and push results into UI queue
處理 API 回應並將結果推送到 UI 佇列
"""
# 定義要輸出的欄位 (避免重複程式碼)
response_fields = [
"status_code", "text", "headers", "content", "history",
"encoding", "cookies", "elapsed", "request_method",
"request_url", "request_body"
]

for field in response_fields:
# 取得多語言字典中的對應文字
label = language_wrapper.language_word_dict.get(field, "")
value = response_data.get(field)
api_testka_ui_queue.put(f"{label}{value}")


class APITestkaGUIThread(QThread):
"""
GUI Thread for executing API tests asynchronously
GUI 執行緒,用於非同步執行 API 測試
"""

def __init__(self):
super().__init__()
self.url = None
self.test_method = None
self.param = None
self.url: str | None = None
self.test_method: str | None = None
self.param: dict | None = None

def run(self):
"""
Run the API test in a separate thread
在獨立執行緒中執行 API 測試
"""
test_response = test_api_method_httpx(
http_method=self.test_method,
test_url=self.url,
params=self.param
)
if test_response is not None and isinstance(test_response, dict):
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("status_code") +
str(test_response.get("response_data").get("status_code")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("text") +
str(test_response.get("response_data").get("text")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("headers") +
str(test_response.get("response_data").get("headers")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("content") +
str(test_response.get("response_data").get("content")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("history") +
str(test_response.get("response_data").get("history")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("encoding") +
str(test_response.get("response_data").get("encoding")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("cookies") +
str(test_response.get("response_data").get("cookies")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("elapsed") +
str(test_response.get("response_data").get("elapsed")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("request_method") +
str(test_response.get("response_data").get("request_method")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("request_url") +
str(test_response.get("response_data").get("request_url")))
api_testka_ui_queue.put(language_wrapper.language_word_dict.get("request_body") +
str(test_response.get("response_data").get("request_body")))

if test_response and isinstance(test_response, dict):
_process_response(test_response.get("response_data", {}))

78 changes: 59 additions & 19 deletions je_api_testka/gui/main_widget.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import json

from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QComboBox, QTabWidget, QFormLayout, \
QPushButton, QTextEdit
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QComboBox, QTabWidget, QFormLayout, QPushButton, QTextEdit
)

from je_api_testka.gui.api_testka_gui_thread import APITestkaGUIThread
from je_api_testka.gui.language_wrapper.multi_language_wrapper import language_wrapper
from je_api_testka.gui.message_queue import api_testka_ui_queue


class APITestkaWidget(QWidget):
"""
API Testka 主視窗元件
Main widget for API Testka GUI
"""

def __init__(self, parent=None):
super().__init__(parent)

# 建立主版面配置
# Create main layout
main_layout = QVBoxLayout()
# URL row

# -------------------------------
# URL 與 Method 選擇區
# URL and HTTP Method selection
# -------------------------------
url_layout = QHBoxLayout()
url_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("url")))

self.url_input = QLineEdit()
url_layout.addWidget(self.url_input)

self.method_combobox = QComboBox()
self.method_combobox.addItems([
language_wrapper.language_word_dict.get("get"),
Expand All @@ -33,60 +47,86 @@ def __init__(self, parent=None):
url_layout.addWidget(self.method_combobox)
main_layout.addLayout(url_layout)

# -------------------------------
# Tabs for Param, Auth, Header, Body
# -------------------------------
self.tabs = QTabWidget()
self.param_tab = QWidget()
self.auth_tab = QWidget()
self.header_tab = QWidget()
self.body_tab = QWidget()

self.tabs.addTab(self.param_tab, language_wrapper.language_word_dict.get("param"))
# 可依需求開啟其他 Tab
# Uncomment if needed
# self.tabs.addTab(self.auth_tab, "Auth")
# self.tabs.addTab(self.header_tab, "Header")
# self.tabs.addTab(self.body_tab, "Body")

layout = QFormLayout()
# Param Tab layout
param_layout = QFormLayout()
self.param_input = QLineEdit()
layout.addRow(language_wrapper.language_word_dict.get("param"), self.param_input)
self.param_tab.setLayout(layout)
param_layout.addRow(language_wrapper.language_word_dict.get("param"), self.param_input)
self.param_tab.setLayout(param_layout)

main_layout.addWidget(self.tabs)

# Send button
# -------------------------------
# Send Button
# -------------------------------
self.send_button = QPushButton(language_wrapper.language_word_dict.get("send"))
self.send_button.clicked.connect(self.start_api_request)
main_layout.addWidget(self.send_button)

# Log panel
# -------------------------------
# Log Panel
# -------------------------------
self.log_panel = QTextEdit()
self.log_panel.setReadOnly(True)
main_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("response_and_log")))
main_layout.addWidget(self.log_panel)

# Param
self.test_api_thread = None
# -------------------------------
# Thread + Timer
# -------------------------------
self.test_api_thread: APITestkaGUIThread | None = None
self.pull_log_timer = QTimer()
self.pull_log_timer.setInterval(20)
self.pull_log_timer.setInterval(20) # 每 20ms 檢查一次佇列
self.pull_log_timer.timeout.connect(self.pull_log)

self.setLayout(main_layout)

def start_api_request(self):
"""
開始 API 測試請求
Start API request in a separate thread
"""
self.log_panel.clear()
self.pull_log_timer.stop()
self.pull_log_timer.start()

self.test_api_thread = APITestkaGUIThread()
self.test_api_thread.url = self.url_input.text()
self.test_api_thread.test_method = self.method_combobox.currentText().lower()
param = self.param_input.text()
if param.strip() != "":

# 嘗試解析 JSON 參數
# Try to parse JSON parameters
param_text = self.param_input.text().strip()
if param_text:
try:
self.test_api_thread.param = json.loads(param)
except json.decoder.JSONDecodeError:
pass
self.test_api_thread.start()
self.test_api_thread.param = json.loads(param_text)
except json.JSONDecodeError:
# 若 JSON 格式錯誤,忽略並繼續
# Ignore invalid JSON and continue
self.test_api_thread.param = None

self.test_api_thread.start()

def pull_log(self):
if not api_testka_ui_queue.empty():
"""
從佇列中拉取訊息並顯示在 Log Panel
Pull messages from queue and display in log panel
"""
while not api_testka_ui_queue.empty():
message = api_testka_ui_queue.get_nowait()
self.log_panel.append(str(message))
self.log_panel.append(str(message))
31 changes: 27 additions & 4 deletions je_api_testka/gui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,40 @@


class APITestkaUI(QMainWindow, QtStyleTools):
"""
API Testka 主視窗
Main window for API Testka application
"""

def __init__(self):
super().__init__()
self.id = language_wrapper.language_word_dict.get("application_name")

# 設定應用程式 ID (Windows 平台需要,讓工作列顯示正確圖示與名稱)
# Set application ID (required on Windows for correct taskbar icon and name)
self.id: str = language_wrapper.language_word_dict.get("application_name")

if sys.platform in ["win32", "cygwin", "msys"]:
from ctypes import windll
windll.shell32.SetCurrentProcessExplicitAppUserModelID(self.id)

# 設定字體樣式
# Set font style
self.setStyleSheet(
f"font-size: 12pt;"
f"font-family: 'Lato';"
"font-size: 12pt;"
"font-family: 'Lato';"
)

# 套用 Material Design 主題
# Apply Material Design theme
self.apply_stylesheet(self, "dark_amber.xml")
self.api_testka_widget = APITestkaWidget()

# 建立並設定主控件
# Create and set main widget
self.api_testka_widget: APITestkaWidget = APITestkaWidget()
self.setCentralWidget(self.api_testka_widget)

if "__main__" == __name__:
app = QApplication(sys.argv)
window = APITestkaUI()
window.show()
sys.exit(app.exec())
Loading