10 年前,自动化测试人员如果要编写 E2E 测试,主要使用 Selenium。每个有机会使用该解决方案的人都应该会记得设置、编写和调试是多么不愉快。在此过程中还创建了一些更有趣的自动化工具,例如 Webdriver.io、TestCafe、Nightwatch.js 和 Puppeteer。但是我们今天不会关注那些,因为我想谈谈Cypress和Playwright中测试自动化的几个问题。Cypress 和 Playwright是目前为测试实现创建理想工具的顶级框架。
两个框架,相似……
Cypress 是一种基于 JavaScript 的前端测试工具。设计为对开发人员友好,它直接在浏览器中运行。 Cypress 的第一个版本于 2015 年发布,并于 2018 年开始真正流行,并于 2020 年筹集了 4000 万美元的 B 轮融资。他们以开源为荣,他们相信这将帮助他们在竞争中取得领先。Playwright(微软),于2020年发布。它是一个用于端到端测试的跨浏览器自动化库,也是开源的。它最初是一个 JavaScript 库,但现在已经扩展到还支持 Python、NET 和 Java。它是专门为网络自动化而创建的。
尽管这两个框架都很年轻,但它们提供了许多很棒的工具,使测试实施变得有趣而不乏味。在本文中,我们将重点关注十几个选定的类别,在我看来,这些类别对开发人员和测试工程师至关重要。质量保证是我们认真对待的事情。
它们如下:
1.与元素交互
2.导航
3.处理警报
4.内嵌框架支持
5.等待
6.要求语言支持
所讨论的示例是针对前端部分的 TheInternet 应用程序和 API 部分的 Reqres.in 实现的。
与元素交互
在第一类中,将比较 Cypress 和 Playwright 的元素交互示例及其断言。测试任务是转到复选框页面,验证项目的数量,验证第一个项目未被选中,选择一个项目,然后验证该项目是否被正确选择。
Cypress:
describe('Interacting with elements', () => {
it('First example', () => {
cy.visit('/checkboxes');
cy.get('[type="checkbox"]')
.should(($elm) => {
expect($elm).to.have.length(2);
})
.eq(0)
.should('not.be.checked')
.check()
.should('be.checked');
});
it('Second example', () => {
cy.visit('/checkboxes');
cy.get('[type="checkbox"]').then(($elm) => {
expect($elm).to.have.length(2);
expect($elm[0]).not.be.checked;
cy.wrap($elm[0])
.click()
.then(($elm) => {
expect($elm).to.be.checked;
});
});
});
});
Playwright:
const { test, expect } = require('@playwright/test');
test('Interacting with elements', async ({ page }) => {
await page.goto('/checkboxes');
const checkboxes = page.locator('[type="checkbox"]');
const firstCheckbox = checkboxes.nth(0);
await expect(await checkboxes.count()).toEqual(2);
await expect(firstCheckbox).not.toBeChecked();
await firstCheckbox.check();
await expect(firstCheckbox).toBeChecked();
});
在 Cypress 的实现中,代码乍一看可能比 Playwright 的可读性差一些,为了对给定元素执行操作,必须从 cy.get() 链接。在 Playwright 中,由于每个用户都能毫无问题地理解,可以轻松地将租户分配给变量。通常对元素执行复杂的操作与所谓的callback hell 相关联。在我看来,微软团队的实现肯定更令人赏心悦目。
导航
下一个类别将验证框架如何处理在 Cypress 与 Playwright 中打开新页面的问题。在测试中,用户单击链接,他们将被重定向到新选项卡中的页面。
Cypress:
describe('Multiple windows', () => {
it('Windows support - not supported', () => {
cy.visit('/windows');
cy.get('[href="/windows/new"]').click();
cy.get('h3').should('have.text', 'New Window');
});
it('Windows support - removing "target" attribute', () => {
cy.visit('/windows');
cy.get('[href="/windows/new"]').invoke('removeAttr', 'target').click();
cy.location('pathname').should('eq', '/windows/new');
cy.get('h3').should('have.text', 'New Window');
});
});
Playwright:
const { test, expect } = require('@playwright/test');
test('Windows support', async ({ page, context }) => {
await page.goto('/windows');
let [newPage] = await Promise.all([
context.waitForEvent('page'),
page.locator('[href="/windows/new"]').click(),
]);
await newPage.waitForLoadState();
const newWindowElement = newPage.locator('h3');
expect(newPage.url()).toContain('/windows/new');
await expect(newWindowElement).toHaveText('New Window');
});
不幸的是,Cypress 不支持在一次测试会话期间打开新窗口,如果被测试的应用程序需要它,这对很多人来说都是一个问题。但是有一个技巧可以绕过这个限制。但是,如果需要留在原始页面上,将无法访问它,因此双方之间的任何交互仍然很困难。使用 Playwright,可以打开的窗口数量没有限制。用户可以完全控制他想要验证的内容。它可以随时与任何上下文相关。
处理警报
警报通常是“旧”JavaScript 的残余,如今它们在现代应用程序中越来越不常见。不幸的是,在自动化过程中,并不总是能接触到现代框架。需要能够处理警报。在这个例子中,将分析Alert、Confirm和Prompt的例子。
Cypress:
describe('Handling Alerts in browser', () => {
beforeEach(() => {
cy.visit('/javascript_alerts');
});
it('Click "OK" on JS Alert', () => {
cy.contains('Click for JS Alert').click();
cy.on('window:alert', (alert) => {
expect(alert).to.eq('I am a JS Alert');
});
cy.on('window:confirm', () => true);
cy.get('#result').should('have.text', 'You successfully clicked an alert');
});
it('Click "OK" on JS Confirm', () => {
cy.contains('Click for JS Confirm').click();
cy.on('window:confirm', (str) => {
expect(str).to.equal(`I am a JS Confirm`);
});
cy.on('window:confirm', () => true);
cy.get('#result').should('have.text', 'You clicked: Ok');
});
it('Click "Cancel" on JS Confirm', () => {
cy.contains('Click for JS Confirm').click();
cy.on('window:confirm', (str) => {
expect(str).to.equal(`I am a JS Confirm`);
});
cy.on('window:confirm', () => false);
cy.get('#result').should('have.text', 'You clicked: Cancel');
});
it('Fill JS Prompt', () => {
cy.window().then(($win) => {
cy.stub($win, 'prompt').returns('This is a test text');
cy.contains('Click for JS Prompt').click();
});
cy.get('#result').should('have.text', 'You entered: This is a test text');
});
});
Playwright:
const { test, expect } = require('@playwright/test');
test.describe('Handling Alerts in browser', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/javascript_alerts');
});
test('Click "OK" on JS Alert', async ({ page }) => {
page.on('dialog', (dialog) => {
expect(dialog.message()).toBe('I am a JS Alert');
dialog.accept();
});
const button = page.locator('button >> text=Click for JS Alert');
await button.click();
const result = page.locator('#result');
await expect(result).toHaveText('You successfully clicked an alert');
});
test('Click "OK" on JS Confirm', async ({ page }) => {
page.on('dialog', (dialog) => {
expect(dialog.message()).toBe('I am a JS Confirm');
dialog.accept();
});
const button = page.locator('button >> text=Click for JS Confirm');
await button.click();
const result = page.locator('#result');
await expect(result).toHaveText('You clicked: Ok');
});
test('Click "Cancel" on JS Confirm', async ({ page }) => {
page.on('dialog', (dialog) => {
expect(dialog.message()).toBe('I am a JS Confirm');
dialog.dismiss();
});
const button = page.locator('button >> text=Click for JS Confirm');
await button.click();
const result = page.locator('#result');
await expect(result).toHaveText('You clicked: Cancel');
});
test('Fill JS Prompt', async ({ page }) => {
page.on('dialog', (dialog) => {
expect(dialog.message()).toBe('I am a JS prompt');
dialog.accept('This is a test text');
});
const button = page.locator('button >> text=Click for JS Prompt');
await button.click();
const result = page.locator('#result');
await expect(result).toHaveText('You entered: This is a test text');
});
});
Playwright 使用相同的实现来处理所有类型的警报。用户可以轻松地验证给定窗口的内容,例如,选择他感兴趣的按钮。就 Cypress 而言,原生弹出窗口的处理不够完善。在每种情况下,3 种类型的窗口和运行无头测试的代码都不同,用户无法查看窗口是否得到了正确处理。
内嵌框架支持
在实施测试时,通常需要使用 iframe,例如以外部支付网关的形式。过去,使用 iframe 对自动化测试人员来说是个大问题——现在这个过程要容易得多。在此挑战中,将尝试获取给定的 iframe 并将文本输入其中。
Cypress:
it('Iframe support', () => {
cy.visit('/iframe');
const iframe = cy
.get('#mce_0_ifr')
.its('0.contentDocument.body')
.should('be.visible')
.then(cy.wrap);
iframe.clear().type('Some text').should('have.text', 'Some text');
});
Playwright:
const { test, expect } = require('@playwright/test');
test('Iframe support', async ({ page }) => {
await page.goto('/iframe');
const frame = page.frameLocator('#mce_0_ifr');
const frameBody = frame.locator('body');
await frameBody.fill('');
await frameBody.fill('Some text');
await expect(frameBody).toHaveText('Some text');
});
在 Playwright 中,使用 frameLocator() 方法可以轻松访问 Iframe。要在 Iframe 中执行操作,使用类似的定位器 (),可以在其中自由执行操作,例如键入文本、单击元素或用户执行的其他操作。另一方面,Cypress并不那么容易支持 iframe。如果要在iframe中执行操作,需要安装npm cypress-iframe包,然后将插件添加到commands.js中。最后,在测试或自定义命令中,创建一个辅助函数,在其中传递要对其执行操作的元素。同样,我的印象是 Cypress 比 Playwright 更难执行简单的 Iframe 操作。你怎么认为?
等待
较旧的框架在等待加载缓慢的元素时总是存在问题,这有时会导致添加硬超时以验证给定元素的可见性。它非常坚持保持代码不稳定。
Cypress:
it('Waiting for lazy elements', () => {
cy.visit('/dynamic_loading/2');
cy.contains('button', 'Start').click();
// Elements is loading longer then global timeout: 5_000
cy.get('#finish', { timeout: 10_000 }).should('be.visible').should('have.text', 'Hello World!');
});
Playwright:
const { test, expect } = require('@playwright/test');
test('Waiting for lazy elements', async ({ page }) => {
await page.goto('/dynamic_loading/2');
await page.getByText('Start').click();
const finish = page.locator('#finish');
// Elements is loading longer then global timeout: 5_000
await expect(finish).toBeVisible({ timeout: 10_000 });
await expect(finish).toHaveText('Hello World!');
});
在 Cypress 和 Playwright 中,如果需要等待一个元素,不需要做任何额外的工作。唯一的额外工作是向元素添加 Cypress 超时。 Cypress 时不时地使用 cy.get,然后在页面上检查给定元素是否存在。如果该元素在默认超时(5_000m s)内不存在,则认为该元素不会出现。可以轻松地在全局范围内增加超时,或者仅针对具有延长加载时间的元素。在 Playwright 中,此机制可用于网络优先断言。在这种情况下,确保元素可见,然后验证其可见性。两种解决方案都令人满意,因为不必等待持续 10_000 毫秒的特定超时,一次可以是 4_500 毫秒,然后 9_500 毫秒,如果用时少于 10 秒,测试将通过。
Requests
测试 Web 应用程序不仅是关于前端元素的验证,一个常见的工作元素也是验证 API 端的东西。可以直接从 Cypress 和 Playwright 框架中的测试代码拦截和发送请求。我们也有模拟查询的能力(请留意关于这个主题的另一篇文章,因为这个主题绝对值得深入研究)。可以直接从 Cypress 和 Playwright 框架中的测试代码拦截和发送请求。在这个简单的示例中,要执行的任务是针对特定用户向 Reqres.in 应用程序发送一个简单的“GET”查询。
Cypress:
it('Request support - "then" example', () => {
cy.request('GET', 'https://reqres.in/api/users/2').then(({ status, body }) => {
expect(status).to.equal(200);
const { email, id } = body.data;
expect(typeof email).to.be.equal('string');
expect(typeof id).to.be.equal('number');
expect(email).to.be.equal('janet.weaver@reqres.in');
expect(id).to.be.equal(2);
});
});
it('Request support - "chain" example', () => {
cy.request('GET', 'https://reqres.in/api/users/2').as('response');
cy.get('@response').its('status').should('eq', 200);
cy.get('@response')
.its('body.data')
.then(({ email, id }) => {
expect(typeof email).to.be.equal('string');
expect(typeof id).to.be.equal('number');
expect(email).to.be.equal('janet.weaver@reqres.in');
expect(id).to.be.equal(2);
});
});
Playwright:
const { test, expect } = require('@playwright/test');
test.use({
baseURL: 'https://reqres.in',
});
test('Request support', async ({ request }) => {
const response = await request.get('/api/users/2');
await expect(response).toBeOK();
const body = await response.json();
const { email, id } = body.data;
expect(typeof email).toBe('string');
expect(typeof id).toBe('number');
expect(email).toEqual('janet.weaver@reqres.in');
expect(id).toEqual(2);
});
Cypress 和 Playwright 中请求的实现几乎是彼此的双胞胎。在 Cypress 中,有一个 .request() 方法,它将 URL 和要执行的方法作为参数,然后在回调中,得到一个响应,在解构之后,可以将其分为状态、正文、持续时间等在下一步中,用户使用来自 Chai.js 库的简单断言。在 Playwright 中,有一个同名的方法来发出请求并指定要使用的 REST API 方法。作为此解决方案的一大优势,可以将给定查询的响应分配给变量,可以随时使用 .json() 方法获取感兴趣的变量。如果目标是验证来自服务器的响应,或者如果愿意,可以使用 .status() 。检查状态码。在 Cypress 中,为了能够访问答案,应该将答案保存为别名,然后通过访问别名,以适当的方式验证其他内容。亲自看看哪种实施方式更适合。
语言支持选择
自动化测试框架的关键方面之一是该工具支持的编程语言。在整个团队使用特定语言编写的情况下,使用相同语言编写测试以在合并请求期间支持整个团队要容易得多。目前,前端的很大一部分是用 JavaScript 或 TypeScript 编写的。两者都得到 Cypress 和 Playwright 的支持!
但是,如果团队使用 Java、Python 或 .NET (C#) 编写,则只有 Playwright 才能完成这项工作。许多测试人员在将 Selenium 与 Java 或 PyTest 结合使用方面具有丰富的经验,因此如果不会 JavaScript,则入门门槛不涉及需要学习一门新语言。如果选择 Cypress,团队有效执行测试需要 JS 知识。