对于测试从业者来说,手工测试是一个绕不过去的坎。当年第一份工作进了一家互联网公司。入职第一天就被师父"拉去干活",至今印象深刻,是一个投顾管理平台(主要功能是为用户做理财产品和资讯推荐)。主要工作就是让我结合Excel里写好测试用例对web页面进行测试,说白了就是点点点。测试新人嘛,这些对于我来说挺新鲜的,但是随着时间的流逝,不到几个月就感觉有点不对了,手工测试完全是个机械化的工作,长此以往,会让你的大脑形成固化思维,在测试过程中大脑得到的测试价值边际效应是递减的,所以这也就解释了大部分手工测试人员普遍测试积极性不高,对未来充满焦虑的原因。
穷则思变,当时作为小白的我向身边的测试老手了解到可以学习UI自动化测试。那时起,测试之路仿佛有了一盏灯塔,让我对其充满向往。随着从事测试行业工作的年限增长,测试经验也越来越丰富,期间大大小小也做了非常多的项目。渐渐地对UI测试自动化也做了些许积累。
随着UI自动化技术的不断发展,此间涌现出一大批优秀的自动化框架,例如Selenium、UIrecoder、Cypress、Playwright等。市面上介绍Selenium、UIrecoder的文章比较多,而我此前也写过二者实践相关的文章,这里不做过多赘述,本文主要介绍近年大火的Cypress、Playwright框架,从二者提供的功能如元素交互、定位、处理告警、iframes、等待、requests进行对比,并通过案例详细呈现二者在使用上的区别。
回到10年前,如果自动化测试人员想编写E2E测试用例,或许只能选择Selenium别无他法。而有使用Selenium开发测试用例的同学大多深有感触,开发用例前必须先做好很多设置上的准备工作,总之使用Selenium编写和调试用例不是让人愉快的经历。随着前端自动化技术的发展,Cypress和Playwright脱颖而出,是目前UI自动化理想的测试框架。

尽管这两个框架都很“young”,但它们提供了许多很棒的功能,使前端测试的实现变得十分有趣。
本文重点从大家比较关心的功能进行对比,为大家进行UI自动化的选型提供参考。

第一个例子介绍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不太容易理解,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来说,可以打开的窗口数量没有限制,用户可以完全控制他想要验证的内容,可以随时与任何上下文相关。
alerts弹窗对大多数用户来说不是很友好,事实上alerts在当下大多数应用程序中越来越不常见。但是在自动化测试过程中,我们仍然会接触到很多触发alerts弹窗的测试场景。
下面这个案例就是比较两个框架处理alerts弹窗的方法。
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支持用相同的实现处理所有类型的alerts,用户可以轻松地验证alerts弹窗的内容。例如,选择你要测试的按钮。
而Cypress对本地弹窗的处理就显得不太友好。用例中有三种不同类型的弹窗,Cypress则需要使用不同的测试代码,这样的测试代码就显得比较冗余。
在测试过程中往往需要使用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中执行操作,我们使用类似的frame.locator ()方法,就可以在其中执行任何操作,例如写入文本、单击元素操作。
反观Cypress则并不容易处理iframe。如果要在Iframe中执行操作,我们需要安装npm cypress-iframe包,然后将插件添加到command.js。最后,在测试用例中,我们需要创建一个辅助函数,在其中传递要在iframe中执行操作的元素。
一些比较老的测试框架始终存在等待缓慢加载元素的问题(例如selenium),有时候我们会在用例中添加过长超时时间以保证被测元素能加载出来来增加用例的稳定性,但是这样就大大降低了测试用例的执行效率。下面且看Cypress和Playwright是如何处理页面等待场景的。
Cypress:
it('Waiting for lazy elements', () => {cy.visit('/dynamic_loading/2');cy.contains('button', 'Start').click();// Elements is loading longer then global timeout: 5_000cy.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_000await expect(finish).toBeVisible({ timeout: 10_000 });await expect(finish).toHaveText('Hello World!');
});
通过代码可以直观地看到,在Cypress和Playwright中,如果需要等待一个元素,你不需要做任何额外的工作,只需要在用例中对cy.get()方法添加timeout,然后使用.should('be.visible')检查页上的元素是否存在。如果元素在默认超时 (5_000ms) 仍不存在,则认为该元素不会出现。
在Playwright中,这种机制可以用于web-first断言。在这种情况下,我们确保元素可见,然后验证其可见性。
当然,相对于“过时”的UI测试框架,这两种解决方案都令人满意。因为我们不必持续等待10_000毫秒的固定超时时间,只要在超时时间内元素可见 测试用例都是通过的。
web测试不只是对页面元素的测试,有时候我们还要验证请求到的后端API接口返回的内容。在Cypress和Playwright框架中,可以直接在测试代码里拦截和发送请求。
测试用例:请求查询接口/api/users/2,对返回的内容进行断言。
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对发送HTTP请求对实现方式几乎一样。
在Cypress,我们使用.request()方法将URL和我们要执行的方法作为参数,然后在回调中得到响应,响应结果包括status,body、duration等。
在Playwright中,同样有request.get()方法用于发送HTTP请求,这里不做过多赘述。
在UI自动化框架选型中,一个重要参考点就是 自动化测试框架支持哪些编程语言。因为在整个团队用特定语言编写的情况下,使用相同的语言编写测试用例可以更好的做测试工作,这这方面,无疑Playwright更具优势。
Playwright支持 Java、Python、.NET (C #)。
Cypress支持JavaScript。
那么,正在读文章的你支持哪种UI自动化框架呢,评论区告诉我。
上一篇:22级第三次比赛题解