Build a Lightweight Python GUI for Testing REST and SOAP APIs (with .exe Packaging)
This tutorial walks you through creating a compact Python Tkinter GUI that can send GET/POST requests, handle JSON or XML payloads, support Bearer tokens, clean illegal URL characters, and be packaged into a standalone Windows executable using PyInstaller.
In everyday development we often need to test backend Web APIs, whether RESTful or SOAP. While tools like Postman exist, a lightweight, customizable, and standalone GUI can be more convenient for specific scenarios.
Why build your own tool? It offers a minimal dependency footprint, allows custom features such as token handling and XML payloads, and runs independently of a browser.
Key features include:
Support for GET and POST methods
Customizable request headers
JSON and XML request bodies
Bearer token authentication
Automatic cleaning of illegal URL characters
Modern GUI using ttkthemes
Ability to package as a Windows .exe without requiring a Python runtime
The interface is divided into several sections: a URL input field, method selector (GET/POST), content‑type selector (JSON/XML), optional token input, header editor, body editor, and a response display area showing status code and response content.
Full Python Code (with comments)
<code>import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import requests
from ttkthemes import ThemedStyle
import re
def send_request():
raw_url = entry_url.get()
method = var_method.get()
headers_text = entry_headers.get("1.0", tk.END)
body_text = entry_body.get("1.0", tk.END).strip()
token = entry_token.get().strip()
use_token = var_use_token.get()
content_type = var_content_type.get() # "JSON" or "XML"
# Clean URL
url = raw_url.strip()
url = ''.join(c for c in url if '\u0020' <= c <= '\uFFFF')
if not url.startswith(("http://", "https://")):
messagebox.showerror("Error", "Please enter a valid URL starting with http:// or https://")
return
try:
headers = {}
for line in headers_text.strip().split('\n'):
if ':' in line:
key, value = line.split(':', 1)
headers[key.strip()] = value.strip()
# Add token if needed
if use_token and token:
headers["Authorization"] = f"Bearer {token}"
# Set Content-Type
if content_type == "JSON":
headers["Content-Type"] = "application/json"
elif content_type == "XML":
headers["Content-Type"] = "application/xml"
# Send request
if method == "GET":
response = requests.get(url, headers=headers)
elif method == "POST":
if content_type == "JSON":
import json
try:
json_body = json.loads(body_text)
response = requests.post(url, headers=headers, json=json_body)
except json.JSONDecodeError as e:
messagebox.showerror("JSON Error", str(e))
return
elif content_type == "XML":
response = requests.post(url, headers=headers, data=body_text)
else:
messagebox.showerror("Error", "Unsupported method!")
return
text_response.delete("1.0", tk.END)
text_response.insert(tk.END, f"Status Code: {response.status_code}\n\n")
try:
text_response.insert(tk.END, response.json())
except Exception:
text_response.insert(tk.END, response.text)
except Exception as e:
messagebox.showerror("Error", str(e))
# ================= GUI ===================
app = tk.Tk()
app.title("WebService Test Tool - JSON/XML Support")
app.geometry("950x800")
app.resizable(True, True)
style = ThemedStyle(app)
style.set_theme("arc")
bg_color = "#f2f2f2"
fg_color = "#333"
accent_color = "#4a90e2"
style.configure("TButton", padding=6, relief="flat", background=accent_color, foreground=fg_color, font=('Segoe UI', 10))
style.map("TButton", background=[('active', '#357ABD')], foreground=[('pressed', 'white'), ('active', 'white')])
style.configure("TRadiobutton", background=bg_color, font=('Segoe UI', 10))
style.configure("TLabel", background=bg_color, font=('Segoe UI', 10))
style.configure("TCheckbutton", background=bg_color, font=('Segoe UI', 10))
app.configure(bg=bg_color)
# Token area
frame_token = ttk.Frame(app)
frame_token.pack(pady=10, padx=20, fill='x')
var_use_token = tk.BooleanVar()
check_use_token = ttk.Checkbutton(frame_token, text="Use Token", variable=var_use_token)
check_use_token.pack(side='left', padx=10)
ttk.Label(frame_token, text="Token:").pack(side='left')
entry_token = ttk.Entry(frame_token, width=50, show="*", font=('Segoe UI', 10))
entry_token.pack(side='left', padx=5, fill='x', expand=True)
# URL input
frame_url = ttk.Frame(app)
frame_url.pack(pady=10, padx=20, fill='x')
ttk.Label(frame_url, text="URL:").pack(anchor="w")
entry_url = ttk.Entry(frame_url, width=80, font=('Segoe UI', 11))
entry_url.pack(fill='x')
# Method selection
frame_method = ttk.Frame(app)
frame_method.pack(pady=5, padx=20, fill='x')
ttk.Label(frame_method, text="Method:").pack(anchor="w")
var_method = tk.StringVar(value="GET")
ttk.Radiobutton(frame_method, text="GET", variable=var_method, value="GET").pack(side="left", padx=10)
ttk.Radiobutton(frame_method, text="POST", variable=var_method, value="POST").pack(side="left", padx=10)
# Content type selection
frame_content_type = ttk.Frame(app)
frame_content_type.pack(pady=5, padx=20, fill='x')
ttk.Label(frame_content_type, text="Content Type:").pack(anchor="w")
var_content_type = tk.StringVar(value="JSON")
ttk.Radiobutton(frame_content_type, text="JSON", variable=var_content_type, value="JSON").pack(side="left", padx=10)
ttk.Radiobutton(frame_content_type, text="XML", variable=var_content_type, value="XML").pack(side="left", padx=10)
# Headers input
frame_headers = ttk.Frame(app)
frame_headers.pack(pady=10, padx=20, fill='x')
ttk.Label(frame_headers, text="Headers (Key: Value)").pack(anchor="w")
entry_headers = scrolledtext.ScrolledText(frame_headers, height=5, wrap=tk.WORD, font=("Consolas", 10), bg="#ffffff")
entry_headers.pack(fill='x')
# Body input
frame_body = ttk.Frame(app)
frame_body.pack(pady=10, padx=20, fill='x')
ttk.Label(frame_body, text="Body (JSON or XML)").pack(anchor="w")
entry_body = scrolledtext.ScrolledText(frame_body, height=12, wrap=tk.WORD, font=("Consolas", 10), bg="#ffffff")
entry_body.pack(fill='x')
# Send button
btn_send = ttk.Button(app, text="🚀 Send Request", command=send_request)
btn_send.pack(pady=15)
# Response output
frame_response = ttk.Frame(app)
frame_response.pack(pady=10, padx=20, fill='both', expand=True)
ttk.Label(frame_response, text="Response:").pack(anchor="w")
text_response = scrolledtext.ScrolledText(frame_response, height=15, wrap=tk.WORD, font=("Consolas", 10), bg="#f9f9f9", fg="#000000")
text_response.pack(fill='both', expand=True)
app.mainloop()</code>Packaging as a Windows Executable
Install PyInstaller and run the following commands:
<code>pip install pyinstaller</code> <code>pyinstaller --onefile --windowed webservice_gui.py</code>The resulting .exe will appear in the dist/ folder and can be run without a Python environment.
Usage Examples
Example 1: Test a GET endpoint
<code>URL: https://jsonplaceholder.typicode.com/posts
Method: GET
(Body not required)</code>Example 2: Send an XML (SOAP) request
<code>URL: http://example.com/soap-endpoint
Method: POST
Content Type: XML
Body:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://tempuri.org/ns">
<soapenv:Header/>
<soapenv:Body>
<ns:GetData>
<ns:id>123</ns:id>
</ns:GetData>
</soapenv:Body>
</soapenv:Envelope></code> <code>Headers:
Content-Type: application/xml
SOAPAction: "http://tempuri.org/GetData"</code>Conclusion
The tutorial demonstrates how to build a fully functional WebService testing tool with a graphical interface, JSON/XML support, token authentication, automatic URL cleaning, and the ability to package it as a standalone executable. Both beginners and experienced developers can extend this framework to create their own private API testing utilities.
Test Development Learning Exchange
Test Development Learning Exchange
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.