【自动化测试】 pytest 结合 Playwright 实现页面元素在两个区域间拖拽

pytest 结合 Playwright 实现页面元素在两个区域间拖拽

      • [✅ 示例场景](#✅ 示例场景)
      • [1. 创建示例 HTML 页面 (`drag_drop_block.html`)](#1. 创建示例 HTML 页面 (drag_drop_block.html))
      • [2. Pytest + Playwright 测试代码 (`test_block_drag_drop.py`)](#2. Pytest + Playwright 测试代码 (test_block_drag_drop.py))
      • [3. 运行测试](#3. 运行测试)
    • 关联文档

好的,下面是使用 pytest 结合 Playwright 实现页面元素在两个区域间拖拽的完整示例。

这个示例将创建一个包含两个区域(sourcetarget)和一个可拖拽区块的 HTML 页面,然后使用 Playwright 模拟将该区块从一个区域拖拽到另一个区域的操作。

✅ 示例场景

我们将创建一个简单的 HTML 页面,包含:

  1. Source Area : 包含一个可拖拽的区块(例如一个带颜色的 <div>)。
  2. Target Area: 一个空的区域,用于接收被拖拽的区块。
  3. JavaScript : 实现 HTML5 拖放 API,处理 dragstart, dragover, 和 drop 事件,以便将元素从 source 移动到 target

1. 创建示例 HTML 页面 (drag_drop_block.html)

将以下 HTML 代码保存为 drag_drop_block.html 文件。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Block Drag & Drop Example</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 40px;
        }

        .area {
            width: 300px;
            height: 200px;
            border: 2px dashed #ccc;
            display: inline-block;
            vertical-align: top;
            margin: 20px;
            padding: 10px;
            position: relative;
        }

        #source-area {
            background-color: #e0f7fa; /* Light Blue */
        }

        #target-area {
            background-color: #f3e5f5; /* Light Purple */
        }

        .draggable-block {
            width: 100px;
            height: 100px;
            background-color: #f44336; /* Red */
            color: white;
            text-align: center;
            line-height: 100px; /* Vertically center text */
            cursor: move; /* Show move cursor */
            user-select: none; /* Prevent text selection */
            position: absolute; /* Position within parent */
            top: 30px;
            left: 50px;
        }

        .block-content {
            font-size: 12px;
        }

        /* Optional: Visual feedback during drag */
        .draggable-block.dragging {
            opacity: 0.5;
        }

        /* Success indicator */
        .success-indicator {
            color: green;
            font-weight: bold;
            display: none; /* Hidden initially */
        }

        #target-area.success .success-indicator {
            display: block;
        }
    </style>
</head>
<body>

    <h1>Block Drag & Drop Test</h1>

    <div id="source-area" class="area">
        <h3>Source Area</h3>
        <div id="draggable-block" class="draggable-block" draggable="true">
            <span class="block-content">Drag Me!</span>
        </div>
    </div>

    <div id="target-area" class="area">
        <h3>Target Area</h3>
        <p class="success-indicator">Block dropped successfully!</p>
    </div>

    <script>
        const draggableBlock = document.getElementById('draggable-block');
        const sourceArea = document.getElementById('source-area');
        const targetArea = document.getElementById('target-area');

        draggableBlock.addEventListener('dragstart', function(event) {
            event.dataTransfer.setData("text/plain", "block-id"); // Optional: Set data
            // Add a class for visual feedback
            this.classList.add('dragging');
        });

        draggableBlock.addEventListener('dragend', function(event) {
            // Remove visual feedback
            this.classList.remove('dragging');
        });

        targetArea.addEventListener('dragover', function(event) {
            event.preventDefault(); // Crucial: Allows dropping
        });

        targetArea.addEventListener('drop', function(event) {
            event.preventDefault(); // Crucial: Allows dropping

            // Move the block from source to target
            // We know the block is the only draggable element in source
            sourceArea.removeChild(draggableBlock);
            targetArea.appendChild(draggableBlock);

            // Optional: Add success class to target area
            targetArea.classList.add('success');
        });

        // Allow dropping back into source area too (for demo purposes)
        sourceArea.addEventListener('dragover', function(event) {
            event.preventDefault();
        });

        sourceArea.addEventListener('drop', function(event) {
            event.preventDefault();

            // Move the block from target back to source
            targetArea.removeChild(draggableBlock);
            sourceArea.appendChild(draggableBlock);

            // Remove success class from target area
            targetArea.classList.remove('success');
        });
    </script>

</body>
</html>

说明:

  • draggable="true": 必须在可拖拽的元素上设置此属性。
  • CSS : 设置了 position: absolute 以便在容器内精确定位区块。
  • JavaScript :
    • dragstart: 设置拖拽数据(可选),添加视觉反馈类。
    • dragend: 移除视觉反馈类。
    • dragover: 必须 调用 event.preventDefault(),否则 drop 事件不会触发。
    • drop: 必须 调用 event.preventDefault()。然后执行元素的移动逻辑(removeChild + appendChild)。
    • 为了演示双向拖拽,source-area 也监听了 dragoverdrop

2. Pytest + Playwright 测试代码 (test_block_drag_drop.py)

首先,确保安装了必要的库:

bash 复制代码
pip install pytest playwright
playwright install

然后,将以下 Python 代码保存为 test_block_drag_drop.py

python 复制代码
# test_block_drag_drop.py
import pytest
from playwright.sync_api import Page, expect

# Pytest fixture to provide a fresh browser page for each test
@pytest.fixture(scope="function")
def page(context):
    """Creates a new page for each test function."""
    page = context.new_page()
    yield page
    page.close()

def test_drag_block_from_source_to_target(page: Page):
    """
    Tests dragging a block from the source area to the target area.
    """
    # 1. Navigate to the HTML file
    # Update the path to point to where you saved the HTML file
    page.goto("file:///absolute/path/to/your/drag_drop_block.html")

    # 2. Define selectors for the draggable block and the target area
    source_area_selector = "#source-area"
    target_area_selector = "#target-area"
    draggable_block_selector = "#draggable-block"

    # 3. Verify initial state: block is in source area
    expect(page.locator(source_area_selector + " " + draggable_block_selector)).to_be_attached()
    expect(page.locator(target_area_selector + " " + draggable_block_selector)).not_to_be_attached()
    expect(page.locator(f"{target_area_selector} .success-indicator")).not_to_be_visible()

    # 4. Perform the drag and drop operation
    page.drag_and_drop(draggable_block_selector, target_area_selector)

    # 5. Verify final state: block is in target area
    expect(page.locator(target_area_selector + " " + draggable_block_selector)).to_be_attached(message="Block should be in target area after drop.")
    expect(page.locator(source_area_selector + " " + draggable_block_selector)).not_to_be_attached(message="Block should not be in source area after drop.")
    expect(page.locator(f"{target_area_selector} .success-indicator")).to_be_visible(message="Success indicator should be visible in target area.")

def test_drag_block_back_to_source(page: Page):
    """
    Tests dragging the block back from the target area to the source area.
    """
    # 1. Navigate to the HTML file
    page.goto("file:///absolute/path/to/your/drag_drop_block.html")

    # 2. Define selectors
    source_area_selector = "#source-area"
    target_area_selector = "#target-area"
    draggable_block_selector = "#draggable-block"

    # 3. First, move the block to the target area (using the previous test's logic or just do it here)
    # Initial state check
    expect(page.locator(source_area_selector + " " + draggable_block_selector)).to_be_attached()
    expect(page.locator(target_area_selector + " " + draggable_block_selector)).not_to_be_attached()

    # Drag to target first
    page.drag_and_drop(draggable_block_selector, target_area_selector)

    # Confirm it's in target
    expect(page.locator(target_area_selector + " " + draggable_block_selector)).to_be_attached()
    expect(page.locator(source_area_selector + " " + draggable_block_selector)).not_to_be_attached()
    expect(page.locator(f"{target_area_selector} .success-indicator")).to_be_visible()

    # 4. Now, drag the block back from target to source
    page.drag_and_drop(draggable_block_selector, source_area_selector)

    # 5. Verify final state: block is back in source area
    expect(page.locator(source_area_selector + " " + draggable_block_selector)).to_be_attached(message="Block should be back in source area after second drop.")
    expect(page.locator(target_area_selector + " " + draggable_block_selector)).not_to_be_attached(message="Block should not be in target area after second drop.")
    expect(page.locator(f"{target_area_selector} .success-indicator")).not_to_be_visible(message="Success indicator should be hidden after moving block back to source.")

说明:

  • Fixture page: 为每个测试函数提供一个新的浏览器页面实例,并在测试结束后自动关闭,确保测试隔离。
  • page.goto() : 导航到你的本地 HTML 文件。务必更新 file:///... 后面的路径为你实际存放 drag_drop_block.html 的绝对路径。
  • page.drag_and_drop(source, target): Playwright 提供的核心方法,用于模拟拖放操作。它会处理底层的鼠标事件序列。
  • expect(...): Playwright 的断言库,用于验证 DOM 状态(元素是否存在、是否可见等)。它具有内置的智能等待机制。

3. 运行测试

在终端中,切换到包含 test_block_drag_drop.pydrag_drop_block.html 的目录,然后运行:

bash 复制代码
pytest test_block_drag_drop.py -v
  • -v 选项提供更详细的输出。

如果配置正确,你应该会看到类似以下的输出,并且浏览器窗口会短暂出现以执行测试:

复制代码
============================= test session starts ==============================
platform linux -- Python 3.x.y, pytest-x.x.x, pluggy-x.x.x
rootdir: /path/to/your/test/directory
collected 2 items

test_block_drag_drop.py::test_drag_block_from_source_to_target PASSED    [ 50%]
test_block_drag_drop.py::test_drag_block_back_to_source PASSED           [100%]

============================== 2 passed in 3.12s ===============================

这表明两个测试(从 source 到 target,以及从 target 回到 source)都成功通过了。

关联文档

【Python知识】使用 Playwright for Python 从零开始搭建自动化测试方案

【自动化测试】Playwright for Python 与 Allure 集成完整指南

【自动化测试】pytest 语法与核心概念

相关推荐
先鱼鲨生2 小时前
自动化框架pytest
运维·自动化·pytest
程序员杰哥1 天前
Pytest自动化测试框架实战
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·pytest
Yorlen_Zhang2 天前
Python pytest assert 断言
python·servlet·pytest
纯纯沙口4 天前
pytest - 基础使用教程
pytest
问道飞鱼4 天前
【自动化测试】pytest 语法与核心概念
自动化测试·pytest·playwright
zUlKyyRC4 天前
基于一阶RC模型,FFRLS+EKF算法的电池SOC在线联合估计Matlab程序
pytest
我的xiaodoujiao6 天前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 40--完善优化 Allure 测试报告显示内容
python·学习·测试工具·pytest
我的xiaodoujiao6 天前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 41--自定义定制化展示 Allure 测试报告内容
python·学习·测试工具·pytest