playwright TypeScript快速上手,选择器,定位器
1. 什么是 TypeScript?
TypeScript 是由微软开发的一种开源编程语言,是 JavaScript 的超集,支持 ECMAScript 6 标准。TypeScript 增强了 JavaScript 的开发体验,提供了静态类型检查和现代化的编程特性。
2. 安装与设置
在开始之前,请确保您已安装 Node.js。可以在 Node.js 官网 下载和安装最新的 LTS 版本。
2.1 安装 TypeScript
在终端中运行以下命令安装 TypeScript:
npm install -g typescript
2.2 初始化 TypeScript 项目
创建一个新的项目文件夹并初始化 TypeScript 配置文件:
mkdir ts-demo
cd ts-demo
tsc --init
3. 基本语法
3.1 变量与类型
TypeScript 中的变量可以显式声明类型:
let isDone:boolean=false;
let age: number =30;
let name:string="Alice";
let list: number[]=[1,2,3];
let tuple:[string, number]=["hello",10];
3.2 函数
函数声明和参数类型:
function add(x: number, y: number): number {
return x + y;
}
let result = add(2, 3); // result 类型为 number
类与接口
TypeScript 支持面向对象编程,包括类和接口。
类:
class Animal{
name:string;
constructor(name:string){
this.name = name;
}
speak():string{
return`${this.name} makes a noise.`;
}
}
let dog =newAnimal("Dog");
console.log(dog.speak());
接口:
interface Person{
firstName:string;
lastName:string;
sayHi:()=>string;
}
let customer:Person={
firstName:"John",
lastName:"Doe",
sayHi:():string=>{return"Hi there!"}
};
模块
TypeScript 使用 import 和 export 关键字进行模块化管理。
math.ts:
export function add(x: number, y: number): number {
return x + y;
}
main.ts:
import { add } from './math';
let result = add(5, 3);
console.log(result);
4. 高级特性
4.1 泛型
泛型使得函数和类可以适应多种类型。
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString");
let output2 = identity<number>(100);
4.2 类型推断
TypeScript 可以根据初始值自动推断变量类型。
let someValue = "this is a string"; // 类型推断为 string
4.3 类型守卫
类型守卫用于在运行时检查类型。
function isString(x: any): x isstring{
returntypeof x ==="string";
}
function printValue(value:string| number){
if(isString(value)){
console.log(value.toUpperCase());
}else{
console.log(value.toFixed(2));
}
}
5. 在 Playwright 中使用 TypeScript
以下是一个简单的 Playwright 测试示例,展示了如何使用 TypeScript 编写自动化测试脚本。
5.1 安装 Playwright
npm install playwright
5.2 编写测试脚本
创建一个新的文件 index.ts,并编写以下测试脚本:
import { chromium,Browser,Page}from'playwright';
(async ()=>{
const browser:Browser= await chromium.launch({ headless:false});
const page:Page= await browser.newPage();
await page.goto('https://example.com');
const title:string= await page.title();
console.log(`页面标题是: ${title}`);
await browser.close();
})();
5.3 运行测试脚本
在终端中运行以下命令编译并执行 TypeScript 脚本:
npx ts-node index.ts
此时应会启动一个 Chromium 浏览器窗口,并导航到示例网站,随后打印页面标题。
小结
入门教程,介绍了对TypeScript的基本语法,并能够掌握编写简单的TypeScript脚本。在掌握这些基础知识后,可以进一步探索 TypeScript的高级特性,并结合 Playwright编写更复杂的自动化测试脚本。希望帮你快速上手ts语法。
选择器与定位器
6. 选择器和定位器简介
选择器是用于匹配页面 DOM 中元素的查询,而定位器是基于选择器的强大抽象,提供了与元素交互和遍历 DOM 的功能。
6.1 Playwright选择器
Playwright选择器是一种用于匹配DOM元素的字符串查询,可以使用CSS、XPath、文本内容、伪选择器或自定义属性。例如:
// 使用 CSS 选择器
const $buttonByCSS = page.locator('css=button');
// 使用 XPath 选择器
const $buttonByXPath = page.locator('xpath=//button');
// 使用伪选择器
const $buttonByRole = page.getByRole('button');
自定义选择器示例
await selectors.register('shadow', createShadowSelector);
const $options = page.locator('shadow=x-custom-select option');
await $options.count();
6.2 Playwright定位器
定位器是基于选择器的抽象,提供了选择元素、与它们交互以及遍历 DOM 子树的方法。定位器具有更新性和自动等待特性。示例:
// 找到带有 `name="submit"` 的按钮
const $submitButton = page.getByRole('button', { name: 'submit' });
await $submitButton.click();
还可以链接定位器以提高特异性,而无需求助于长选择器查询。例:
const $taskList = page.locator('ul#tasks');
const $taskLinks = $taskList.locator('a');
const $completedTasks = $taskLinks.filter({
has: page.locator('input[type="checkbox"]:checked'),
});
7. 选择器最佳实践
7.1 优化选择器特异性
选择器应足够精确以明确识别目标元素,但不应过于复杂。避免过于僵化或过于宽松的选择器。示例:
// 🔴 BAD: 选择器基于脆弱的结构假设
const $link = page.locator('li:nth-child(3) > a');
// 🟢 GOOD: 选择器基于区分特征
const $link = page.locator('a[href*="privacy"]');
// 🔴 BAD: 锚定到不可变文本
const $button = page.locator('button',{hasText:'Sign up'});
// 🟢 GOOD: 健壮,基于匹配到选择器
const $button = page.locator('button',{
hasText:/(sign|start|subscribe|launch)/i,
});
// 🔴 BAD: 不要用易变的class属性
const $input = page.locator('input.email');
// 🟢 GOOD: 基于功能的属性稳定性更好
const $input = page.locator('input[type="email"]');
// 🔴 BAD: 少用精准的层级关系
const $list = page.locator('footer > ul.inline-links > li');
// 🟢 GOOD: 适配性更好的描述
const $list = page.locator('footer:last-of-type li',{
has: page.locator('a'),});
7.2 首选语义定位器方法
优先使用语义属性而非样式或功能属性。Playwright 提供的语义方法,如 getByRole、getByLabel 等,有助于选择器的稳定性和代码可读性。示例:
// 🟢 辅助testid,需要研发配合支持
const $modal = page.getByTestId('delete-modal');
// 🟢 通过角色选择按钮
const $button = page.getByRole('button',{ hasText:'Buy'});
// 🟢 通过目标文本内容,而不是基于html结构定位
const $input = page.getByLabel('Email');
const $search = page.getByPlaceholder(/^search/i);
const $image = page.getByAltText('Profile Picture');
const $icon = page.getByTitle('Info',{exact:false});
// 🟢 通过文本内容+正则匹配
const $dialog = page.getByText(/^confirm/i);
7.3 链定位器,而不是选择器
避免长选择器查询字符串,使用链定位器提高可读性和调试性。
在子树中向下钻取
链接定位器就像向草图添加图层;每增加一笔,图像就会更加细化。从广泛的定位器开始,并使用 、 、 和条件参数等方法逐渐缩小到目标元素。.filter()、.first()、.last()
示例:
/ Find the first article about BrowserCat
const $articles = page.locator('article');
const $aboutBrowserCat = $articles.filter({
hasText: /BrowserCat/i
});
const $firstArticle = $aboutBrowserCat.first();
这种方法将关注点分开,将定位器与逻辑实体联系起来,并保持代码的可读性和适应性。如果布局发生更改,你可能只需要调整父定位器,而无需解开多个复杂字符串。
按内容筛选{has}、{hasNot}
有时,不仅需要根据元素本身的属性来选择元素,还需要根据它与其他元素的关系来选择元素。使用{has}和{hasNot}参数,可以清楚地定义这些关系,从而为选择器创建可靠的上下文。 示例:
// Find the first article with an image
const $articles = page.locator('article');
const $withImages = $articles.filter({
has: page.locator('img'),
});
const $firstArticle = $withImages.first();
这些参数充当有关父元素中某些元素存在的断言。这增加了可用于创建良好查询模式的稳定、唯一属性的数量。
7.4 使用正确的等待策略
默认情况下,Playwright的定位器将等待页面上出现至少一个匹配元素。在许多情况下,这就是你所需要的,但随着你对自动化了解深入,你容易发现这个策略可能需要调整。比如多元素的例子:
// 🟢 GOOD: 单一元素,等待策略有效
const $modal = page.locator('#signup-modal');
// 🔴 BAD: 多个元素,策略会失效
const $confetti = page.locator('.confetto');
等待全局状态
上面的“动态五彩纸屑”问题可以通过等待全局状态归一化来解决。
// 等待至少 100
await page.waitForFunction(async () => {
return await page.locator('.confetto').count() >= 100;
});
const $confetti = page.locator('.confetto');
page.waitForFunction()将运行该函数,直到返回为真。这使你可以编写同样强大的非常干净的代码。
检查定位器状态
当至少有一个匹配的 DOM 节点连接到 DOM 时,定位器通常会解析。但是,Playwright 提供了一些其他方便的方法:
// Attached
const $attached = page.getByRole('button')
.waitFor({state:'attached'});
// Detached
const $detached = page.getByRole('button')
.waitFor({state:'detached'});
// Visible
const $attached = page.getByRole('button')
.waitFor({state:'visible'});
// Hidden when detached, `visibility: hidden`, or `display: none`
const $attached = page.getByRole('button')
.waitFor({state:'hidden'});
状态经常会派上用场,因为许多动态 HTML 通过管理可见性而不是动态创建新的子树来工作。visible
设置自定义超时
默认情况下,Playwright 将尽快解析定位器。但是,如果你正在等待动态添加的元素列表,你可能更愿意设置延长超时。
// Wait for a waterfall of injected JS script tags
const $dynamicScripts = page.locator('script')
.waitFor({timeout: 60_000});
也就是说,请谨慎使用该方法。如果发现自己依赖于这种不精确的工具,可能需要三思而后行以寻找更好的解决方案。毕竟,通过定位允许脚本继续执行的精i确事件,你可以节省大量时间。
等待 DOM 事件
有时,最好回到基础并依靠 DOM 来确保在继续使用选择器之前加载必要的内容。
例如,假设需要在截取页面屏幕截图之前加载特定图像。可以使用该事件来确保出现这种情况:load
const $logo = page.getByRole('img')
.and(page.getByAltText('BrowserCat'));
$logo.evaluate(($el)=>{
returnnewPromise((resolve)=>{
$el.addEventListener('load', resolve,{once:true});
});
});
// Logo is loaded!
await $logo.screenshot();
Playwright API 通过这个简单的构造,可以直接访问浏览器上下文。使用它来深入了解金属,并直接控制脚本的流程
7.5 优雅地处理错误
处理自动化脚本中的不确定性,包括多种定位策略、跳过可选内容和明确控制流程。示例:
多种定位策略获取评级
const maybeRatings = await Promise.all([
page.locator('.rating-value').textContent(),
page.locator('.star-rating img.star').count(),
page.locator('meta[name="description"]')
.getAttribute('content')
.then((str)=>(/\n\.\n+ stars/u).exec(str ??'')?.[0]),
]);
const bestRating = maybeRatings.reduce((pick, rating)=>{
return pick ?? parseFloat(rating)||null;
},null);
脚本尝试多种策略来捕获评级,测试每个结果以确保其看起来像实际评级编号。
悄悄跳过可选内容
并非所有内容对于成功完成脚本都是必不可少的。例如,假设您想抓取图书产品页面,但不能始终保证页数可用。在这种情况下,您只希望脚本在未找到临界值(如书名)时失败,否则则通过。
const title = await page.locator('.book-title')
.textContent();
const author = await page.locator('.author-name')
.textContent();
const pageCount = await page.locator('.page-count')
.textContent();
const details ={
title,
author,
pageCount: parseInt(pageCount)||null,
};
assert(!!details.title &&!!details.author);
该函数演示了选择性地包含可选数据。如果存在,它会丰富结果。但如果缺席,则不构成失败。
明确您的控制流程
如果脚本中充斥着条件语句和错误处理,则控制流可能会变得不明确。Playwright 的定位器可以帮助我们简化流程。
在以下示例中,我们仅与目录(如果存在)进行交互。
const $toc = page.locator('aside.contents');
if(await $toc.count()>0){
const $lastLink = await $toc.locator('a').last();
await $lastLink.click();
const depth = await page.evaluate(()=> window.scrollY);
console.log(`Scrolled to depth: ${depth}`);
}else{
console.log('No table of contents found.');
}
此示例演示了一个明确的控制流,其中目录的存在会触发条件行为,否则不会导致错误。
重试失败的手势
在某些情况下,可能希望脚本重试某个操作,直到满足特定条件,例如滚动数字骰子直到出现特定数字。
const $d20 = page.getByRole('button',{name:'Roll d20'});
const $result = page.locator('#d20-result');
page.waitForFunction(async ()=>{
await $d20.click();
const roll = await $result.textContent().then((str)=> parseInt(str));
return roll ===20;
});
此代码片段滚动虚拟骰子,重试手势,直到达到预期结果,展示受控且持久的交互模式。
如所见,这些策略为你的 Web 自动化任务提供了准确性和容错性,确保与各种网页、内容结构和意外情况的顺利交互。
7.6 使用异步交互
Playwright的websocket通信模型和异步API使并行任务成为可能。示例:
// 并行运行任务
const result = await Promise.all([
page.locator('#long-running-calculation').click(),
page.waitForResponse((res) => /calculation-results/.test(res.url())),
]).then(() => {
return page.locator('.calculation-results').textContent();
});
7.7 使用自定义选择器扩展Playwright
定义自定义选择器以满足特定需求。示例:
await selectors.register('data-state',()=>({
query(root, selector){
return root.querySelector(`[data-state="${selector}"]`);
},
queryAll(root, selector){
returnArray.from(root.querySelectorAll(`[data-state="${selector}"]`));
},
}));
const $active = page.locator('data-state=active');
await $active.click();
7.8 使用Page对象模型
页面对象模型(POM)通过封装页面行为和元素提高代码可维护性。示例:
// 定义登录页面对象
classLoginPage{
constructor(page){
this.page = page;
}
get $usernameInput(){returnthis.page.getByLabel('Username');}
get $passwordInput(){returnthis.page.getByLabel('Password');}
get $loginButton(){returnthis.page.getByText('Login');}
async login(username, password){
await this.$usernameInput.fill(username);
await this.$passwordInput.fill(password);
await this.$loginButton.click();
}
}
// 使用页面对象
const page = await browser.newPage();
const loginPage =newLoginPage(page);
await loginPage.login('user@example.com','p1a2s3s4word!');
后续步骤
通过优化选择器、使用语义方法、正确等待策略和页面对象模型,可以显著提高Playwright脚本的可维护性和性能。继续探索并应用这些最佳实践,以实现更强大的自动化解决方案。
更多推荐



所有评论(0)