use: { + channel:'chrome', /* Base URL to use in actions like `await page.goto('/')`. */ // baseURL: 'http://127.0.0.1:3000', // 堆代码 duidaima.com /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', },我们虽然改成了使用浏览器来运行,但是启动的浏览器也是一个无痕模式,不包含任何缓存信息。
import { test, expect, type Page } from "@playwright/test"; // 堆代码 duidaima.com test("登录", async ({page, context}) => { await context.addCookies([ { name: "sessionid", value: "xxx", path: "/", domain: ".juejin.cn", }, { name: "sessionid_ss", value: "xxx", path: "/", domain: ".juejin.cn", }, ]); await page.goto("https://juejin.cn/"); });打开 chrome 控制台,复制 cookies, 添加到代码中
import { test, expect, type Page } from "@playwright/test"; test("登录", async ({page, context}) => { await context.addCookies([ { name: "sessionid", value: "xxx", path: "/", domain: ".juejin.cn", }, { name: "sessionid_ss", value: "xxx", path: "/", domain: ".juejin.cn", }, ]); await page.goto("https://juejin.cn/"); + await context.storageState({ path: 'state.json' }); });并且在 playwright.config.ts 中,配置存储位置。
import { test, expect } from '@playwright/test'; test('test', async ({ page }) => { await page.goto('https://juejin.cn/'); await page.getByRole('button', { name: '去签到' }).click(); await page.getByRole('button', { name: '立即签到' }).click(); await page.getByRole('button', { name: '去抽奖' }).click(); await page.getByText('免费抽奖次数:1次').click(); await page.getByRole('button', { name: '收下奖励' }).click(); });录制完成后,直接运行代码可能会报错,我们需要调整一下,因为有些文本是异步请求实现的,有些事件是请求成功后绑定的,在手动录制时,因为已经响应完成,因此没问题,我们加上 2 句延迟。
test("test", async ({ page }) => { await page.goto("https://juejin.cn/"); + await page.waitForTimeout(1000); await page.getByRole("button", { name: /去签到|已签到/ }).click(); + await page.waitForTimeout(1000); await page.getByRole("button", { name: /今日已签到|立即签到/ }).click(); await page.getByRole("button", { name: "去抽奖" }).click(); const lotteryElement = await page.$("#turntable-item-0"); const buttonText = await lotteryElement?.textContent(); if (buttonText === "免费抽奖次数:1次") { await lotteryElement?.click(); await page.getByRole("button", { name: "收下奖励" }).click(); } else { expect( page.locator("#turntable-item-0", { hasText: /单抽/ }) ).toBeDefined(); } });便可以运行成功,注意这里我使用了 waitForTimeout 这个 api 在官网中已经被标记了废弃(deprecate)。实际测试场景中请使用改用网络事件、选择器变得可见等信号。
await page.goto("https://juejin.cn/"); await page.waitForResponse((res) => res.url().includes("/user_api/v1/incentive_activity/award_after_login") ); await page.getByRole("button", { name: /去签到|已签到/ }).click(); await page.waitForResponse((res) => res.url().includes("/growth_api/v2/get_today_status") ); await page.getByRole("button", { name: /今日已签到|立即签到/ }).click();等待接口响应成功后再出发点击事件。还有一点就是,自动录制的代码,一般使用了语义化定位方法,比如getByRole、getByText,这些定位器往往不够准确,改动代码会导致测试用例失效。因此我们可以使用 locator 定位器来替换。
await page.locator( '#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input' ).click(); await page .locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input') .click();在 vscode 中可以使用 Pick locator 快速活动当前的 dom 定位。
test("签到的状态根据接口返回显示", async ({ page }) => { await page.goto("https://juejin.cn/user/center/signin"); const promise = await page.waitForResponse((res) => res.url().includes("/growth_api/v2/get_today_status") ); const res = await promise.json(); if (res.data.check_in_done) { await expect(page.locator(".signedin")).toHaveText("今日已签到"); } else { await expect(page.locator(".signedin")).toHaveText("立即签到"); await page.getByRole("button", { name: /立即签到/ }).click(); await page.getByRole("button", { name: "去抽奖" }).click(); //调整到抽奖页面 await expect(page).toHaveURL(/user\/center\/lottery/); } });抽奖页面,根据接口返回显示抽奖次数和奖品
test("根据接口返回显示抽奖次数", async ({ page }) => { await page.goto("https://juejin.cn/user/center/lottery"); const promise = await page.waitForResponse((res) => res.url().includes("/growth_api/v1/lottery_config/get") ); const res = await promise.json(); const lotteryNames = res.data.lottery.map((item) => { if (item.unlock_count === 0) { return new RegExp(item.lottery_name); } else { return new RegExp(`再抽${item.unlock_count}次解锁`); } }); await expect(page.locator(".item-container .turntable-item")).toHaveText( lotteryNames ); if (res.data.free_count) { await expect(page.locator("#turntable-item-0")).toHaveText( `免费抽奖次数:${res.data.free_count}次` ); } else { await expect(page.locator("#turntable-item-0")).toHaveText("单抽 200"); } });有了以上断言,我们便可以确保前端页面显示与接口返回显示一致。