You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Playwright测试重复创建博客及删除功能测试失败求助

博客应用Playwright测试问题排查

问题现象

为MERN栈博客编写Playwright测试时,登录、博客创建测试正常通过,但测试博客删除功能时出现以下问题:

  • 测试数据库中Test blog 1和Test blog 2各被创建了两次,浏览器端也显示重复博客
  • 删除测试本身未通过,移除该测试后,A new blog can be created测试出现超时,报错信息显示等待.blog元素超时
  • 手动或通过Postman测试后端增删功能均正常,仅Playwright测试出现异常

测试代码

主测试文件

const { test, expect, beforeEach, describe } = require("@playwright/test");
const { loginWith, createBlog } = require("./helper");

describe("Blog app", () => {
    beforeEach(async ({ page, request }) => {
        await request.post("/api/testing/reset");
        await request.post("/api/users", {
            data: {
                name: "Matti Luukkainen",
                username: "mluukkai",
                password: "salainen",
            },
        });

        await page.goto("/");
    });

    test("Login form is shown", async ({ page }) => {
        await expect(page.getByText("log in to application")).toBeVisible();
        await expect(page.getByTestId("username")).toBeVisible();
        await expect(page.getByTestId("password")).toBeVisible();
        await expect(page.getByRole("button")).toBeVisible();
    });

    describe("Login", () => {
        test("succeeds with correct credentials", async ({ page }) => {
            await loginWith(page, "mluukkai", "salainen");
            await expect(page.getByText("Matti Luukkainen logged in")).toBeVisible();
        });

        test("fails with wrong credentials", async ({ page }) => {
            await loginWith(page, "mluukkai", "wrongpassword");

            const notificationDiv = await page.locator(".notification");
            await expect(notificationDiv).toContainText("wrong username or password");
            await expect(notificationDiv).toHaveCSS("border-style", "solid");
            await expect(notificationDiv).toHaveCSS("color", "rgb(255, 0, 0)");
            await expect(
                page.getByText("Matti Luukkainen logged in")
            ).not.toBeVisible();
        });

        describe("When logged in", () => {
            beforeEach(async ({ page }) => {
                await loginWith(page, "mluukkai", "salainen");
            });

            test("A new blog can be created", async ({ page }) => {
                await createBlog(page, {
                    title: "A blog created by playwright",
                    author: "John Doe",
                    url: "https://google.com/",
                });

                const blog = await page.locator(".blog", {
                    hasText: "A blog created by playwright",
                });
                await expect(blog).toBeVisible();
            });

            describe("And a blog exists", () => {
                beforeEach(async ({ page }) => {
                    await createBlog(page, {
                        title: "Test blog 1",
                        author: "John Doe",
                        url: "https://yahoo.com",
                    });
                    await createBlog(page, {
                        title: "Test blog 2",
                        author: "Jane Doe",
                        url: "https://bing.com",
                    });
                });

                test("A blog can be liked", async ({ page }) => {
                    const blog = await page.locator(".blog", { hasText: "Test blog 1" });
                    await expect(blog).toBeVisible();

                    await blog.getByRole("button", { name: "view" }).click();
                    await blog.getByRole("button", { name: "like" }).click();
                    await expect(blog).toContainText("likes 1");
                });

                test("A blog can be deleted", async ({ page }) => {
                    const blog = await page.locator(".blog", { hasText: "Test blog 2" });
                    await expect(blog).toBeVisible();

                    await blog.getByRole("button", { name: "view" }).click();
                    await blog.getByRole("button", { name: "remove" }).click();

                    page.once("dialog", (dialog) => {
                        console.log(`Dialog message: ${dialog.message()}`);
                        dialog.dismiss().catch(() => {});
                    });

                    await expect(blog).not.toBeVisible();
                });
            });
        });
    });
});

辅助函数文件

exports.loginWith = async (page, username, password) => {
    await page.getByTestId("username").fill(username);
    await page.getByTestId("password").fill(password);
    await page.getByRole("button", { name: "login" }).click();
};

exports.createBlog = async (page, { title, author, url }) => {
    try {
        await page.getByRole("button", { name: "add blog" }).click();
        await page.getByTestId("title").fill(title);
        await page.getByTestId("author").fill(author);
        await page.getByTestId("url").fill(url);
        await page.getByRole("button", { name: "create" }).click();

        await page.locator(".blog", { hasText: title }).waitFor();
    } catch (error) {
        console.error("Error in createBlog:", error);
        throw error;
    }
};

超时错误日志

@
blog_app.spec.js:62
Test timeout of 30000ms exceeded while running "beforeEach" hook.
@
helper.js:15
Error: locator.waitFor: Test timeout of 30000ms exceeded.
Call log:
  - waiting for locator('.blog').filter({ hasText: 'Test blog 1' }) to be visible

问题原因分析

  1. 对话框监听时机错误:删除测试中,page.once("dialog")是在点击remove按钮之后注册的,此时Playwright可能已经触发了对话框事件,导致监听未捕获到对话框,页面因未处理的对话框卡住,测试超时。
  2. 对话框操作错误:使用dialog.dismiss()取消删除操作,导致博客未被删除,测试断言expect(blog).not.toBeVisible()永远无法通过,进一步引发测试流程异常。
  3. waitFor使用不当:辅助函数中用locator.waitFor()等待元素,相比Playwright推荐的expect断言,可靠性更低,容易因页面状态问题引发超时。

解决方案

1. 修正删除测试的对话框处理逻辑

将对话框监听移至点击remove按钮之前,并改为确认删除操作:

test("A blog can be deleted", async ({ page }) => {
    // 提前注册对话框监听,确保捕获事件
    page.once("dialog", async (dialog) => {
        console.log(`Dialog message: ${dialog.message()}`);
        await dialog.accept(); // 确认删除操作
    });

    const blog = page.locator(".blog", { hasText: "Test blog 2" });
    await expect(blog).toBeVisible();

    await blog.getByRole("button", { name: "view" }).click();
    await blog.getByRole("button", { name: "remove" }).click();

    await expect(blog).not.toBeVisible();
});

2. 优化辅助函数的等待逻辑

将locator.waitFor()替换为expect断言,符合Playwright最佳实践:

exports.createBlog = async (page, { title, author, url }) => {
    try {
        await page.getByRole("button", { name: "add blog" }).click();
        await page.getByTestId("title").fill(title);
        await page.getByTestId("author").fill(author);
        await page.getByTestId("url").fill(url);
        await page.getByRole("button", { name: "create" }).click();

        // 用expect断言等待博客元素出现,更可靠
        await expect(page.locator(".blog", { hasText: title })).toBeVisible();
    } catch (error) {
        console.error("Error in createBlog:", error);
        throw error;
    }
};

验证修改效果

  • 删除测试可正常通过,博客被正确删除,数据库中不再出现重复创建的测试博客
  • 移除删除测试后,其他测试也不会再出现超时问题
  • 所有测试均能稳定通过

内容的提问来源于stack exchange,提问作者Stephen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 10:26:04