在编写端到端测试时,我们需要首先找到网页上的元素,然后对其执行用户操作。例如,找到一个链接并点击它。使用 Playwright
的测试生成器是最方便的方法,不仅可以生成测试代码,还可以生成特定元素的定位器。在深入了解如何使用测试生成器之前,先让我们理解什么是链接元素以及定位器是什么。
1. 基础知识
1.1 什么是链接?
HTML` <a> `
元素,也称为锚点元素,是一个交互元素,它创建一个指向另一个页面的超链接,可以是站内链接,也可以是外部链接。它还可以用于链接到同一页面的特定区域,以及用于电子邮件、文件下载或任何URL可以指向的内容。<a>
元素使用 href
属性传递超链接指向的URL。
1.2 什么是定位器?
在 Playwright
中,我们使用“定位器”来表示查找页面元素的方法,具有自动等待和重试功能。自动等待意味着 Playwright
会在执行点击操作之前,对元素进行一系列可操作性检查,如确保元素可见且已启用。
你可以通过以下方式定位元素:
-
• 角色
(role)
-
• 标签
(label)
-
• 占位符
(placeholder)
-
• 文本
(text)
-
• 替代文本
(alt text)
-
• 标题
(title)
-
• 测试ID
(test id)
2. 如何定位链接
HTML
<a>
元素默认具有链接角色,因此我们不需要修改HTML
来获得这个角色。
<a href="/about">About</a>
这意味着我们可以使用 getByRole()
定位器,通过链接角色来定位 <a>
元素,并在其后添加点击方法。
await page.getByRole('link').click();
2.1 添加名称
这将起作用,但如果页面上有多个链接呢?这时需要使用 name
选项。name
是辅助技术用于识别元素的可访问名称,通常取自元素内容、属性或相关元素。
await page.getByRole('link', { name: 'About' }).click();
2.2 使用正则表达式
我们还可以使用正则表达式并忽略大小写,这样无论文本是否包含大写字母,测试都将通过。
await page.getByRole('link', { name: /about/i }).click();
2.3 设置精确匹配
你可以将 exact
设置为 true
以进行精确匹配。如果你有一个包含文本 "About"
的链接和另一个包含文本 "About the Company"
的链接,设置 exact
为 true
将只匹配包含 "About"
的链接。
await page.getByRole('link', { name: 'About', exact: true }).click();
2.4 添加 aria-label
如果链接包含 aria-label
,则将使用 aria-label
的值而不是文本内容。
<a href="/locators" aria-label="read more about locators">Read more</a>
然后我们可以使用 getByRole() 定位器通过 aria-label 的值来定位链接。
await page.getByRole('link', { name: /read more about locators/i }).click();
3. 使用 Playwright
的测试生成器
你可以使用 VS Code
扩展从测试侧边栏点击“record new”
链接来生成代码。
当你点击网站上的元素时,生成器将查看页面并为你找出最佳定位器,优先考虑角色、文本和测试ID
定位器。如果生成器找到多个匹配的元素,它将改进定位器以使其独特和稳健,因此你不必担心由于定位器问题导致的测试失败。
如果你没有使用 VS Code
扩展,Playwright
提供了一个独立的测试生成器 Codegen
。
npx playwright codegen
3.1 选择定位器
你可以通过点击测试侧边栏中的“pick locator”
按钮来选择定位器。
当你将鼠标悬停在浏览器窗口中的任何元素上时,该元素的定位器将突出显示在光标下。如果你点击该元素,定位器将出现在“pick locator”
框中。你可以将其复制到剪贴板并粘贴到测试中。
你还可以通过点击独立测试生成器中的“explore”
按钮来选择定位器。
npx playwright open
3.2 链式定位器
如果你的网站包含重复链接,例如在页眉和页脚中有两个关于链接,那么测试生成器将为每个链接创建一个唯一的定位器。它通过查找链接的易识别祖先并链式定位器来实现这一点。
getByRole('navigation').getByRole('link', { name: 'About' })
对于页脚中的关于链接,测试生成器再次查找祖先并链式两个 getByRole()
定位器,确保只有一个元素匹配该定位器。
getByRole('contentinfo').getByRole('link', { name: 'About' })
3.3 过滤定位器
如果测试生成器不能通过链式定位器给你一个唯一的定位器,它将使用 filter()
方法确保定位器是唯一的。例如,我们可能有两个包含博客文章主题的列表。第一个列表用于过滤博客文章,第二个列表用于显示每篇文章的主题。测试生成器将使用 filter()
方法确保定位器是唯一的。
getByRole('list')
.filter({ hasText: 'architecturedev reljamstackjavascriptlifestylementoringmotivationnuxtperformance' })
.getByRole('link', { name: 'architecture' })
3.4 改进你的定位器
你可能会注意到,通过文本过滤有时会给你一个非常丑陋的定位器,如上例所示,并可能在以后引起问题,特别是如果你添加了另一个主题到过滤器中。
你可以通过使用正则表达式来匹配文本中的某些单词来改进这个过滤器,选择那些通常不会在一篇文章中一起出现的词,如“architecture”
、“mentoring”
和“testing”
。
getByRole('list')
.filter({ hasText: /architecture.*mentoring.*testing/ })
.getByRole('link', { name: 'architecture' })
另一种选择是向第一个 <ul>
元素添加一个 aria-label
值为“topics”
。这不仅有助于提高页面的可访问性,还允许 Playwright
通过角色定位。
<ul aria-label="topics">
//...
</ul>
测试生成器现在将使用这个 aria-label
并通过角色为“topics”
的列表进行定位,从而生成一个唯一的定位器。
小结论
本文未涉及的角色和定位器还有很多。幸运的是,你不必知道哪些是可用的,因为感谢测试生成器,你不必担心在编写测试时选择哪个角色或定位器。这提供了更好的开发体验,并确保你拥有稳健的定位器,无论你是否是测试新手。
如果测试生成器生成了你不满意的定位器,那么可能值得调查DOM
,看看是否可以改进代码以提高可访问性,从而使测试生成器生成更好的定位器。这不仅为你提供了更好的测试,还改进了代码。
最后,从作者自己使用体会来说,比较推荐尝试使用测试生成器,无论是使用VS Code
扩展还是从终端打开它
npx playwright codegen
一旦你熟练掌握之后,就可以自己手写代码而不依赖于生成器了。
使用Playwright
无头模式实现网页抓取,具备资源需求较少,运行速度较快,支持多实例并行等特点
4. 无头浏览器介绍
无头浏览器是一种没有图形用户界面的浏览器,它可以在后台运行。这种浏览器的优点是资源需求少,能够在服务器上轻松运行,并可以同时启动多个实例。无头浏览器可以执行 JavaScript
代码,这使得它们可以完全渲染由前端框架(如 React.js
、Vue.js
、Angular
)构建的现代网页,从而获取完整的网页内容。
5. Playwright无头模式实现网页抓取
5.1 初始化项目
首先,创建一个新的 Node.js
项目并安装 Playwright
:
npm init --yes
npm install playwright
创建一个index.js
文件并添加以下代码,启动浏览器并导航到一个网页:
const playwright =require('playwright');
async function main(){
const browser = await playwright.chromium.launch({ headless:false});
const page = await browser.newPage();
await page.goto('https://finance.yahoo.com/world-indices');
await page.waitForTimeout(5000);
await browser.close();
}
main();
这个脚本初始化一个 Chromium
实例,打开一个新页面,导航到 Yahoo Finance
的世界指数页面,等待 5
秒,然后关闭浏览器。
5.2 使用 Playwright
抓取元素列表
要抓取元素列表,可以定位特定的 HTML
节点并提取所需数据:
const playwright =require('playwright');
async function main(){
const browser = await playwright.chromium.launch({ headless:true});
const page = await browser.newPage();
await page.goto('https://finance.yahoo.com/world-indices');
const market = await page.$eval('#YDC-Lead-Stack-Composite', headerElm =>{
const data =[];
const listElms = headerElm.getElementsByTagName('li');
Array.from(listElms).forEach(elm =>{
data.push(elm.innerText.split('\n'));
});
return data;
});
console.log('Market Composites--->>>>', market);
await page.waitForTimeout(5000);
await browser.close();
}
main();
这个脚本定位 ID
为 YDC-Lead-Stack-Composite
的元素,并提取其子元素 li
的文本内容。
5.3 抓取图片
要抓取图片,可以获取图片标签的 src
属性,并使用 HTTP
请求下载图片:
const playwright =require('playwright');
const axios =require("axios");
const fs =require("fs");
async function saveImages(){
const browser = await playwright.chromium.launch({ headless:true});
const page = await browser.newPage();
await page.goto('https://www.scrapingbee.com/');
const url = await page.$eval(".pdxItem.pdxItem--img-solid img", img => img.src);
const response = await axios.get(url,{ responseType:'arraybuffer'});
fs.writeFileSync("scrappy.svg", response.data);
await browser.close();
}
saveImages();
5.4 截图
Playwright 可以截取整个页面或特定部分的截图:
const playwright =require('playwright');
async function takeScreenShots(){
const browser = await playwright.chromium.launch({ headless:true});
const page = await browser.newPage();
await page.setViewportSize({ width:1280, height:800});
await page.goto('https://finance.yahoo.com/');
await page.screenshot({ path:'my_screenshot.png'});
await browser.close();
}
takeScreenShots();
5.5 使用 XPath
表达式选择器查询
使用 XPath
表达式来定位和查询特定的 DOM
元素:
const playwright =require('playwright');
async function main(){
const browser = await playwright.chromium.launch({ headless:false});
const page = await browser.newPage();
await page.goto('https://stackoverflow.blog/');
const xpathData = await page.$eval('xpath=//html/body/div/header/nav', navElm =>{
let refs =[];
let atags = navElm.getElementsByTagName("a");
for(let item of atags){
refs.push(item.href);
}
return refs;
});
console.log('StackOverflow Links', xpathData);
await page.waitForTimeout(5000);
await browser.close();
}
main();
5.6 提交表单和抓取需要身份验证的页面
自动化表单提交并导航经过身份验证的页面:
const playwright =require('playwright');
async function formExample(){
const browser = await playwright.chromium.launch({ headless:false});
const page = await browser.newPage();
await page.goto('https://github.com/login');
await page.fill('input[name="login"]',"MyUsername");
await page.fill('input[name="password"]',"Secrectpass");
await page.click('input[type="submit"]');
}
formExample();
6. Playwright 与其他工具比较
Playwright
提供跨浏览器支持,并且以其易用性和优秀的文档著称。Puppeteer
也同样易于使用并且有很好的文档,但主要支持 Chromium
。Selenium
更为通用,但启动时间较慢且使用起来可能不如前两者直观。
Playwright
和 Puppeteer
的性能相似,都很快且可靠。相比之下,Selenium
的启动时间较慢,但由于其广泛的语言支持和强大的社区,仍然被广泛使用。
Playwright
是一个强大的网页抓取工具,尤其适合有 Node.js
经验的开发者。它简化了浏览器自动化任务,并提供出色的性能和跨浏览器支持。其活跃的社区和全面的文档使其成为现代网页抓取需求的理想选择。