Cypress vs Playwright——哪个 JavaScript 测试框架更好?

2023-04-30   出处: The Software House  作/译者:Józef Szymala/Yilia


  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 知识。


声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
292° /2923 人阅读/0 条评论 发表评论

登录 后发表评论