测试主题切换器
最后修改日期:2025 年 3 月 22 日
本教程将详细介绍如何构建一个带有主题切换功能的 Flask 应用程序,该应用程序将使用 Selenium 进行测试,并与 Chrome 开发者工具集成。该应用程序包含一个切换开关,用于在浅色和深色主题之间切换,Selenium 测试将验证 DOM 更改,并在执行过程中直观地显示开发者工具。
项目结构
下面将介绍应用程序的结构。
theme_app/ ├── app.py # Flask app ├── static/ │ └── style.css # CSS for themes and toggle ├── templates/ │ └── home.html # Home page with toggle └── test/ ├── __init__.py # Makes test/ a package └── test_app.py # Selenium tests with DevTools
设置 Flask 应用程序
该应用程序是一个简单的 Flask 应用,它提供一个带有主题切换开关的单一页面。该切换开关使用 JavaScript 在浅色和深色主题之间切换,通过向 `<body>` 元素应用 CSS 类来实现。Selenium 测试在单独的线程中启动应用程序,与切换开关进行交互,并使用 Chrome DevTools Protocol (CDP) 来检查 DOM 更改,打开开发者工具以进行视觉确认。
Flask 应用程序
此脚本定义了 Flask 应用程序及其单个路由。
# theme_app/app.py from flask import Flask, render_template app = Flask(__name__) @app.route('/') def home(): return render_template('home.html') if __name__ == '__main__': app.run(debug=True)
`app.py` 文件初始化了一个 Flask 应用程序,并定义了一个渲染 `home.html` 模板的单个路由 (`/`)。`app.run(debug=True)` 命令启动了调试模式下的开发服务器,以便在开发过程中获得实时反馈。
HTML 模板
此模板包含主题切换开关和用于主题切换的 JavaScript。
<!-- theme_app/templates/home.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Theme Switcher App</title> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> </head> <body> <div class="container"> <h1>Welcome to Theme Switcher</h1> <p>Toggle the switch below to change themes.</p> <!-- Toggle Switch --> <label class="switch"> <input type="checkbox" id="theme-toggle"> <span class="slider round"></span> </label> <span class="label-text">Light/Dark Mode</span> </div> <script> const toggle = document.getElementById('theme-toggle'); const body = document.body; // Load saved theme from localStorage if (localStorage.getItem('theme') === 'dark') { body.classList.add('dark-theme'); toggle.checked = true; } // Toggle theme on click toggle.addEventListener('change', () => { body.classList.toggle('dark-theme'); const theme = body.classList.contains('dark-theme') ? 'dark' : 'light'; localStorage.setItem('theme', theme); }); </script> </body> </html>
`home.html` 模板创建了一个包含标题、段落和使用 `style.css` 中的 CSS 样式化的切换开关的页面。JavaScript 在加载时检查 `localStorage` 中保存的主题,如果设置为“dark”,则将 `dark-theme` 类应用于 `<body>`。单击切换开关会切换类并更新 `localStorage`,从而实现主题的持久化。
CSS
此 CSS 文件用于样式化切换开关并定义浅色和深色主题。
/* theme_app/static/style.css */ body { font-family: Arial, sans-serif; transition: background-color 0.3s, color 0.3s; margin: 0; padding: 0; } /* Light theme (default) */ body { background-color: #f0f0f0; color: #333; } /* Dark theme */ body.dark-theme { background-color: #333; color: #f0f0f0; } .container { max-width: 800px; margin: 50px auto; text-align: center; } /* Toggle Switch Styles */ .switch { position: relative; display: inline-block; width: 60px; height: 34px; vertical-align: middle; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: 0.4s; border-radius: 34px; } .slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; transition: 0.4s; border-radius: 50%; } input:checked + .slider { background-color: #2196F3; } input:checked + .slider:before { transform: translateX(26px); } .label-text { margin-left: 10px; font-size: 16px; }
`style.css` 文件定义了浅色主题(背景为 `#f0f0f0`)和深色主题(背景为 `#333`)的样式,并带有平滑过渡。它还样式化了切换开关,使用一个隐藏的复选框和一个滑动的 `.slider` 元素,该元素在选中时会改变颜色和位置。
Selenium 单元测试
此模块包含基于 Selenium 的单元测试,用于验证主题切换器,并在执行期间打开 Chrome 开发者工具。
# theme_app/test/test_app.py import unittest from flask import url_for from app import app as flask_app from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains import threading import time from werkzeug.serving import make_server class TestThemeSwitcher(unittest.TestCase): @classmethod def setUpClass(cls): # Configure Flask app flask_app.config['TESTING'] = True flask_app.config['SERVER_NAME'] = 'localhost:5001' # Start Flask server in a separate thread with explicit stop capability cls.server = make_server('localhost', 5001, flask_app, threaded=True) cls.server_thread = threading.Thread(target=cls.server.serve_forever) cls.server_thread.daemon = False # Non-daemon for explicit control cls.server_thread.start() # Wait for server to start time.sleep(1) # Set up Selenium WebDriver (using Chrome) with DevTools capabilities options = webdriver.ChromeOptions() # Ensure NOT headless so we can see DevTools # options.add_argument('--headless') # Comment this out options.add_argument('--auto-open-devtools-for-tabs') # Auto-open DevTools (optional) cls.driver = webdriver.Chrome(options=options) # Enable DevTools DOM domain cls.driver.execute_cdp_cmd('DOM.enable', {}) @classmethod def tearDownClass(cls): # Disable DOM domain and clean up Selenium cls.driver.execute_cdp_cmd('DOM.disable', {}) cls.driver.quit() # Explicitly stop the Flask server cls.server.shutdown() cls.server_thread.join(timeout=5) # Wait for thread to finish, max 5s if cls.server_thread.is_alive(): print("Warning: Server thread did not stop cleanly") def setUp(self): # Reset browser state before each test self.driver.delete_all_cookies() self.client = flask_app.test_client() def test_home_page_basic(self): """Test the home page for status code and basic content""" response = self.client.get('/') self.assertEqual(response.status_code, 200) self.assertIn(b"<title>Theme Switcher App</title>", response.data) self.assertIn(b'<input type="checkbox" id="theme-toggle">', response.data) self.assertIn(b"body.classList.toggle('dark-theme')", response.data) def test_theme_switching(self): """Test theme switching by inspecting DOM elements and showing DevTools""" driver = self.driver driver.get('https://:5001/') # Wait for the label (which wraps the toggle) to be clickable toggle_label = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CLASS_NAME, "switch")) ) # Open Developer Tools using keyboard shortcut (Ctrl+Shift+I) actions = ActionChains(driver) actions.key_down(Keys.CONTROL).key_down(Keys.SHIFT).send_keys('i')\ .key_up(Keys.SHIFT).key_up(Keys.CONTROL).perform() time.sleep(1) # Give DevTools a moment to open # Get the document node ID for the root (HTML document) document = driver.execute_cdp_cmd('DOM.getDocument', {}) root_node_id = document['root']['nodeId'] # Query the <body> element using DOM.querySelector body_node = driver.execute_cdp_cmd('DOM.querySelector', { 'nodeId': root_node_id, 'selector': 'body' }) body_node_id = body_node['nodeId'] # Get initial attributes of <body> (should not have dark-theme) initial_attributes = driver.execute_cdp_cmd('DOM.getAttributes', {'nodeId': body_node_id}) initial_classes = initial_attributes.get('attributes', []) class_index = initial_classes.index('class') + 1 if 'class' in initial_classes else -1 initial_class_value = initial_classes[class_index] if class_index >= 0 else '' self.assertNotIn('dark-theme', initial_class_value) # Check initial background color for confirmation body = driver.find_element(By.TAG_NAME, 'body') initial_bg = driver.execute_script( "return window.getComputedStyle(arguments[0]).backgroundColor", body ) self.assertEqual(initial_bg, 'rgb(240, 240, 240)') # #f0f0f0 # Click the label to toggle the theme (DevTools should show the change) toggle_label.click() # Wait for theme transition and let you see it in DevTools time.sleep(2) # Increased to give you time to observe # Get updated attributes of <body> (should now have dark-theme) dark_attributes = driver.execute_cdp_cmd('DOM.getAttributes', {'nodeId': body_node_id}) dark_classes = dark_attributes.get('attributes', []) dark_class_index = dark_classes.index('class') + 1 if 'class' in dark_classes else -1 dark_class_value = dark_classes[dark_class_index] if dark_class_index >= 0 else '' self.assertIn('dark-theme', dark_class_value) # Check dark theme background color dark_bg = driver.execute_script( "return window.getComputedStyle(arguments[0]).backgroundColor", body ) self.assertEqual(dark_bg, 'rgb(51, 51, 51)') # #333 # Toggle back to light (DevTools should update) toggle_label.click() time.sleep(2) # Increased to give you time to observe # Get final attributes of <body> (should not have dark-theme again) light_attributes = driver.execute_cdp_cmd('DOM.getAttributes', {'nodeId': body_node_id}) light_classes = light_attributes.get('attributes', []) light_class_index = light_classes.index('class') + 1 if 'class' in light_classes else -1 light_class_value = light_classes[light_class_index] if light_class_index >= 0 else '' self.assertNotIn('dark-theme', light_class_value) # Check light theme background color again light_bg = driver.execute_script( "return window.getComputedStyle(arguments[0]).backgroundColor", body ) self.assertEqual(light_bg, 'rgb(240, 240, 240)') # Optional: Keep browser open longer to inspect DevTools time.sleep(3) if __name__ == '__main__': unittest.main()
`test_app.py` 模块使用 Selenium 测试 Flask 主题切换器应用程序。在 `setUpClass` 中,Flask 应用程序配置为在线程中使用 `werkzeug.serving.make_server` 在端口 5001 上运行,以实现显式的停止控制。使用可选的自动打开开发者工具的非无头 Chrome 实例进行初始化,并通过 CDP 启用 DOM 域。`tearDownClass` 会禁用 DOM 域,关闭浏览器,并通过 `shutdown` 和 `join` 显式停止服务器线程。
`test_home_page_basic` 测试使用 Flask 的测试客户端来验证页面的状态码和内容,检查标题、切换复选框和主题切换脚本。`test_theme_switching` 测试通过 `Ctrl+Shift+I` 打开开发者工具,检查初始的浅色主题(无 `dark-theme` 类,背景为 `#f0f0f0`),切换到深色(验证 `dark-theme` 类和背景为 `#333`),然后切换回浅色,并暂停以允许在开发者工具中进行检查。
要求
在 `requirements.txt` 文件中指定所需的 Python 包(可选但推荐)
flask selenium
使用以下命令安装依赖项
pip install -r requirements.txt
运行应用程序
要手动运行应用程序,请导航到 `theme_app` 目录并执行
flask run
在 `https://:5000` 访问应用程序以手动与主题切换器进行交互。
要运行测试,请导航到 `theme_app` 目录并执行
python -m unittest test.test_app -v
测试将启动 Flask 应用程序,打开带有开发者工具的 Chrome 浏览器,执行主题切换测试,并允许在关闭之前在“Elements”选项卡中直观地检查 DOM 更改。
在本文中,我们创建了一个带有主题切换器的 Flask 应用程序,并使用 Selenium 编写了单元测试来验证其功能,并集成了 Chrome 开发者工具进行可视化调试。
作者
列出 所有 Python 教程。