在页面交互操作中,常常会根据某元素是否存在来决定下一步的操作任务。本文总结了项目中使用Playwright比较常用的方法和技巧。

使用 page.isVisible

用 page.isVisible 检查页面上的元素是否可见。如果可见,则该函数返回 true;否则返回 false。参考下面的例子:

// 已经登录就截图,否则就去登录页
if ( await page.isVisible("text='个人中心'") ) {
    await page.screenshot({ path: 's1.png' });
} else {
    await e2eLogin(page, 'username', 'password');
    await expect(page.getByText('个人中心')).toBeVisible();
    await page.context().storageState({ path: 'user.json' });
}

使用locator.waitFor

await page
    .locator('...')
    .waitFor({ timeout: 5000 })
    .catch(() => {});
    if (await page.locator('...').isVisible()) {
  // ...
}

使用locator+条件

const myElement = page.locator('#elem-id') 
if (await myElement.isVisible()) {
  await myElement.click()
}

使用locator.or

考虑如下场景:

    你想要单击 "新邮件" 按钮,但有时会显示安全设置对话框。在这种情况下,你可以等待 "新邮件" 按钮或对话框并进行相应操作

const newEmail = page.getByRole('button', { name: 'New email' });
const dialog = page.getByText('Confirm security settings');
await expect(newEmail.or(dialog)).toBeVisible();
if (await dialog.isVisible())
  await page.getByRole('button', { name: 'Dismiss' }).click();
await newEmail.click();

使用测试生成器

  • 断言可见性 工具生成 expect(locator).toBeVisible()

  • 断言值 工具生成 expect(locator).toHaveValue()

  •  断言文本 工具生成 expect(locator).toContainText()

参考如下示例:

import { test, expect } from '@playwright/test';

test('test', async ({ page }) => {
  await page
    .goto('https://playwright.nodejs.cn/');
  await page
    .getByRole('link', { name: 'Get started' })
    .click();
  await expect(page
    .getByLabel('Breadcrumbs')
    .getByRole('list'))
    .toContainText('Installation');
  await expect(page
    .getByLabel('Search'))
    .toBeVisible();
  await page
    .getByLabel('Search')
    .click();
  await page
    .getByPlaceholder('Search docs')
    .fill('locator');
  await expect(page
    .getByPlaceholder('Search docs'))
    .toHaveValue('locator');
});
  • page.isVisiable适合判断element是否存在

  • locator+条件是另外一种实现方法

  • page.waitFor({timeout: 1000})可指定等待时间

  • locator.or更适合出现的元素2选1这样的场景

  • 断言生成器也提供了录制代码可视化断言方法

在web自动化测试实践中,常常遇到登录相关的问题:

  • 怎样实现自动化登录?

  • 验证码怎么处理?如何自动化?

  • 多因素验证,如需要输入手机短信验证码怎么办?

  • 微信扫描二维码能自动化吗?

  • 如何记住登录状态?

下面将分享如何制定适当的测试策略,并使用Playwright实现登录保持状态,在后续自动化测试使用,从而加快后续自动化测试,并以b站登录为例进行说明。

2. 制定登录测试验证策略

鉴权,即成功登录web往往是验证用户合法性第一步。从自动化测试角度看

  • 实现自动化登录往往是第一步

  • 不同场景测试都去执行登录又显得很繁琐

  • 如何实现 “一次登录并保持登录” 状态?

从网站开发角度看

  • 最简单的登录功能实现一般包括:用户名、密码、提交按钮

  • 出于现实安全性考虑,往往会增加验证码环节,或者

  • 多因素认证,例如通过手机短信验证码,或者

  • 支持通过app扫码,微信二维码扫码登录 等

从产品开发迭代发布周期来看

  • 在正式发布的线上环境,手工测试验证依然是必不可少的,针对验证码、手机短信验证、app扫码登录、微信扫码登录等场景功能验证依然需要手工方式,基于实现成本,一般不建议完全从自动化角度考虑

  • 在迭代开发的测试环境、开发环境,可以和产研团队协商,从方便自动化测试角度考虑,可以设定特殊的测试账号或者绕过鉴权步骤,或者简化验证手段等等

  • 结合自动化测试(测试环境为主)、手动测试(线上环境为主),针不同目标采用不同测试策略

以上,验证码、二维码 对于自动化都是相对比较困难的问题。通过制定手工测试结合自动化测试不同策略,可以将不同的登录问题区分开来。

无论手动输入验证码登录,扫描二维码登录,或是自动登录,完成成功鉴权后,如果能把对应的浏览器状态保持下来,在后续测试中复用,就可以免重复登录,实现一次登录并保持以加快后续测试执行速度。

在开始使用Playwrgith保存和读取浏览器状态前,先看下浏览器数据保存机制。

3. 数据是如何在浏览器保存的

以b站为例,登录成功后,在后台可以看到cookie、本地存储、会话存储

Cookies、localStorage、sessionStorage都是用于在浏览器中存储数据的机制,但它们在存储方式、生命周期和使用场景上有一些区别。

Cookies:

  • 存储方式: 使用Cookies存储的数据可以设置过期时间,也可以在浏览器关闭后仍然保留。

  • • 生命周期: 可以设置过期时间,可以在浏览器关闭后仍然保留。

  • • 使用场景: 适合存储需要在不同会话之间共享的数据,比如 用户登录状态。

localStorage:

  • 存储方式: 使用localStorage存储的数据会一直存在,除非被主动删除或网站清除浏览数据。

  • • 生命周期: 永久性存储,除非手动删除或网站清除。

  • • 使用场景: 适合存储不会过期的数据,比如用户偏好设置。

sessionStorage:

  • 存储方式: 使用sessionStorage存储的数据仅在当前会话期间有效,关闭标签页或浏览器后会被清除。

  • • 生命周期: 会话期间有效,关闭标签页或浏览器后清除。

  • • 使用场景: 适合存储临时性数据,比如表单数据,在用户关闭标签页后不再需要。

可以针对具体情况分析状态信息如何存储,这个对后面的复用浏览器状态有用。多数网站的登录信息存储在cookie中,也有少数采用会话存储方式。

4. Playwright中如何复用浏览器状态

4.1 本地cookie保存

Playwright保存浏览器状态,参考下面的代码:

// 保存到文件
await page.context().storageState({ path: '.auth/user.json' });

以b站来说,登录成功以后,身份验证信息保存在cookie。

4.2 本地cookie复用

在上一步将cookie保存到文件后,复用就非常简单了,只需要从browser对象生成page对象时候指定storageState为之前保存的文件即可,参考代码:

const page = await browser.newPage(
  { storageState:'.auth/user.json'} );
  //已复用登录后的身份信息,无需重复登录
  await page.goto('https://space.bilibili.com/');

进一步,如果需要整个项目复用的话,需有修改配置文件 playwright.config.ts

export default defineConfig({
  /* Configure projects for major browsers */
  projects: [
    {
      name: 'Microsoft Edge',
      use: { 
        ...devices['Desktop Edge'] ,
        // 复用auth信息
        storageState: '.auth/user.json',
      },      
    },
  ],
});

主要就是 use 这里

4.3 会话存储sessionStorage保存

根据Playwright文档的说法,上面cookie复用的方法可以搞定多数基于本地存储的身份验证了。

但是也有例外

出于安全性等考虑,有些网站的身份验证信息是基于sessionStorage的,用上面的办法保留下来的文件内容是空。并且Playwright也不提供用于持久化会话存储的 API,该如何复用呢?已经验证过下面的例子可用,来自于官网

Authentication | Playwright

const sessionStorage = await page.evaluate(() => JSON.stringify(sessionStorage));
fs.writeFileSync('.auth/session.json', JSON.stringify(sessionStorage), 'utf-8');

4.4 会话存储sessionStorage复用

从文件session.json读取身份验证信息,并在测试中复用:

test.beforeEach(async ({ page }) => {
    const sessionStorage = JSON.parse(fs.readFileSync('.auth/session.json', 'utf-8'));

    await context.addInitScript(storage => {
        for (const [key, value] of Object.entries(storage))
          window.sessionStorage.setItem(key, value);
    }, sessionStorage); });

5. b站登录例子

  • • 需求: 实现b站登录后,在后续测试中可以复用登录信息

  • • 思路: 简单分析后,b站采用cookie保存登录信息,那么可以考虑将cookie内容保存下来,在后续case复用即可;至于登录部分,无论方式是验证码还是app扫码,出于实现成本和稳定性角度,考虑手动方式会更合理

  • • 问题: 如何结合自动化测试和手动测试配合完成?

  • • 解决: Playwright提供了timeout参数,可以设定合理的值比如15秒留给手动操作

5.1 登录case

采用录制(npx playwright codegen https://bilibili.com)等方式生成如下测试case bilibili.spec.ts

import { test, expect, browser } from '@playwright/test';

const authFile = '.auth/user.json';

test('1.保存登录信息', async ({ page }) => {
  await page.goto('https://www.bilibili.com/');
  //扫码登录..
  await expect(page.getByText('登录成功', { exact: true })).toBeVisible({timeout: 15000});
  await page.context().storageState(
    { path: authFile });
  await page.close();
});

test('2.记住登录', async ({ browser }) => {
  const page = await browser.newPage(
    { storageState:authFile } );
  await page.goto('https://space.bilibili.com/');
  await expect(page).toHaveTitle(/个人主页/);
  await page.close();
  await browser.close();
});

说明如下:

  • • 第1个test在b站登录成功后,将浏览器状态保存到.auth/user.json文件中。要注意的是,扫码部分需要配合人工在手机上操作,也可以输入用户名密码完成

  • • 以上登录操作需要在15m成功完成,否则test会失败,timeout:15000,这里也可根据情况调整时间

  • • 第2个test访问b站个人空间,不需要扫码或输入用户名,验证身份信息可以复用

5.2 配置文件

bilibili.spec.ts

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: false,
  reporter: 'html',
  use: {
    video: 'on', 
    locale: 'zh-cn' 
  },

  projects: [    
    {
      name: 'Microsoft Edge',
      use: { 
        ...devices['Desktop Edge'] ,channel: 'msedge',
        //auth
        storageState: '.auth/user.json',
      },
    },
  ],
});
  1. 说明如下

  2. 1. projects配置storageState

  3. 2. testDir设置为./tests目录

  4. 3. use配置了video为on,locale为zh-cn

5.3 video截屏

  • • test1 - 保存登录信息

  1. 说明如下

  2. 1. projects配置storageState

  3. 2. testDir设置为./tests目录

  4. 3. use配置了video为on,locale为zh-cn

总结:

  • 记住登录状态,在后续测试中复用能加快自动化测试,提高执行效率

  • • 浏览器存储身份验证信息,多数位于cookie中,也有sessionStorage

  • • Playwright中storageState可以存储和读取身份验证信息,通常保存在cookie,比如一些token;sessionStorage也可以存储,可以用fs.write保存

  • • 配置文件playwright.config.ts可设置项目参数storageState: '.auth/user.json'来复用经过身份验证的状态,从而免去重复登录

  • • 合理制定自动化测试策略,比如在测试环境多考虑一些自动化测试手段,乃至要求研发侧做适当改造从而加快自动化测试都是有必要的,而线上环境多考虑一些手动测试覆盖

更多推荐