柏树研讨会第7部分:元素操纵 - 表和上传
#javascript #cypress #测试 #qa

表是不同Web元素的集合,通常由多个可编辑/不可编辑的字段组成,以输入,文本区域,按钮,下拉列表等形式组成。

所以,让我们从以前的课程中使用我们的知识来编写一些与表相关的新测试用例:

1:在e2e/elements_manipulation下创建一个新的测试文件,并将其称为tables.cy.js

2:在内部写下以下测试:

/// <reference types="Cypress" />

describe('Tables: Tables actions', () => {
  beforeEach('Navigate to tables page', () => {
    // Visit tables page
    // Table records return to default after page refresh
    cy.visit('/webtables');
  });

  it('Check finding and editing a record', () => {
    // Get the table, find the row with record Alden
    cy.get('.rt-tbody')
      .contains('.rt-tr-group', 'Alden')
      .then((row) => {
        // Click on edit button for Alden record
        cy.wrap(row).find('[title="Edit"]').click();
        // Edit first and last name
        cy.get('#firstName').clear().type('Harvey');
        cy.get('#lastName').clear().type('Specter');
        // Submit edit form
        cy.get('#submit').click();
        // Assert that first and last name are changed
        cy.wrap(row).find('.rt-td').eq(0).should('contain', 'Harvey');
        cy.wrap(row).find('.rt-td').eq(1).should('contain', 'Specter');
      });
  });

  it('Check finding and deleting a record', () => {
    // Get the table, find the row with record Alden
    cy.get('.rt-tbody')
      .contains('.rt-tr-group', 'Alden')
      .then((row) => {
        // Click on delete button for Alden record
        cy.wrap(row).find('[title="Delete"]').click();
      });
    // Assert that table does not contain Alden record
    cy.get('.rt-tbody').should('not.contain', 'Alden');
    // Perform search for Alden record
    cy.get('#searchBox').type('Alden');
    // Assert that Alden record does not exist and "No rows found" message is displayed
    cy.get('.rt-tbody').should('not.contain', 'Alden');
    cy.get('.rt-noData').should('contain', 'No rows found').should('be.visible');
  });

  it('Check search for different age records', () => {
    // Define age group
    const ageGroup = [29, 39, 45, 77];
    // For each age group perform following
    cy.wrap(ageGroup).each((age) => {
      // Type age into search input field
      cy.get('#searchBox').clear().type(age);
      // If age is 77 assert that record doesn't exist in the table, otherwise assert that record is present
      if (age === 77) {
        cy.get('.rt-tbody').find('.rt-tr-group').first().should('not.contain', age);
        cy.get('.rt-noData').should('contain', 'No rows found').should('be.visible');
      } else {
        cy.get('.rt-tbody').find('.rt-tr-group').first().should('contain', age);
        cy.get('.rt-tbody').contains('.rt-tr-group', age).should('have.length', 1);
      }
    });
  });

  xit('Check adding a new record - Bad code practice', () => {
    // Click on Add button
    cy.get('#addNewRecordButton').click();
    // Fill new record form
    cy.get('#firstName').type('Harvey');
    cy.get('#lastName').type('Specter');
    cy.get('#userEmail').type('specter@example.com');
    cy.get('#age').type('40');
    cy.get('#salary').type('700000');
    cy.get('#department').type('legal');
    cy.get('#submit').click();
    // Assert that new record is present in the table with correct values
    cy.get('.rt-tbody')
      .contains('.rt-tr-group', 'Harvey')
      .then((row) => {
        cy.wrap(row).find('.rt-td').eq(0).should('contain', 'Harvey');
        cy.wrap(row).find('.rt-td').eq(1).should('contain', 'Specter');
        cy.wrap(row).find('.rt-td').eq(2).should('contain', '40');
        cy.wrap(row).find('.rt-td').eq(3).should('contain', 'specter@example.com');
        cy.wrap(row).find('.rt-td').eq(4).should('contain', '700000');
        cy.wrap(row).find('.rt-td').eq(5).should('contain', 'legal');
      });
  });

  it('Check adding a new record', () => {
    // Click on Add button
    cy.get('#addNewRecordButton').click();
    cy.fixture('users').then((user) => {
      // Fill new record form
      const columnIDs = Object.keys(user.user1);
      const userData = Object.values(user.user1);

      cy.wrap(columnIDs).each((id, value) => {
        cy.get(`#${id}`).type(userData[value]);
      });
      cy.get('#submit').click();
      // Assert that new record is present in the table with correct values
      cy.get('.rt-tbody')
        .contains('.rt-tr-group', user.user1.firstName)
        .then((row) => {
          cy.wrap(userData).each((value, index) => {
            cy.wrap(row).find('.rt-td').eq(index).should('contain', value);
          });
        });
    });
  });
});

您可以看到,我们在此测试文件中有多个测试。在每个测试(行:7)之前,我们将首先使用表page访问我们的测试页面。要注意的一件事:此页面的编写方式是,如果您修改表中的记录,每个页面请求之后,我们将获得该表的默认状态,这意味着每个测试的默认状态,我们将具有相同的表格状态如果我们通过乳头自动化或手动修改它。

让该测试文件分为每个测试的多个解释:

测试1:检查查找和编辑记录

对于此测试案例,我们想用名称为“ Alden”的记录编辑该行。我们想编辑他的名字和姓氏为Harvey Specter,并检查名称是否在表中更改。

代码说明:

在线上:12我们得到整个桌子主体

线:13我们得到整个身体后,我们正在寻找包含文本的行 - aldenâ

然后行:14,我们正在创建一个函数,该函数将返回该行中的每个数据

line:16我们正在包装行数据,我们正在找到编辑按钮元素,然后单击它

打开编辑模式后,在第18和19行上,我们将首先编辑和姓氏,然后键入新值

线上:21,我们提交表格,模态正在关闭

第23行:从包装的行数据中,我们找到了第一列并断言它包含更新的名字

第24行:从包装的行数据中,我们找到了第二列并断言它包含更新的姓氏

为什么我们不为编辑按钮使用ID,为什么要包装特定的行数据以获取选择器?因为它们具有索引,并且在这种情况下不是唯一的,因此我们要确保我们只更改Alden的记录,以防表中的记录更改以后的顺序(开发人员更改了顺序ETC)。

测试2:检查查找和删除记录

对于此测试案例,我们想从表格中删除记录,在这种情况下 - 带有名字的记录。我们还想通过断言该表不包含删除时包含该记录的记录是否删除记录,并且我们正在检查如果我们搜索表记录没有出现,但是我们有一个文本 - 找不到行 - 。

代码说明:

在线上:30,我们得到了整个桌子主体

第31行:我们正在掌握整个身体之后,我们正在寻找包含文本的行 - aldenâ

第32行:我们正在创建一个函数,该函数将返回该行中的每个数据

第34行:我们正在包装行数据,我们正在查找删除按钮元素,然后单击它

第37行:我们断言桌子的主体不包含记录 - 其他(删除了行)

第39行:我们在搜索框中键入文本âaldenâ搜索整个表格以查看该记录

第41行:我们断言桌子不包含记录的Aldenâ

第42行:我们断言“没有发现的行”是可见的

测试3:检查搜索不同的年龄记录

对于此测试案例,我们想按年龄编号搜索表。可以说,我们想搜索三个现有的年龄记录和一个不存在的年龄记录。我们将搜索每个年龄段,为每个年龄段,我们将断言它们是否存在于表中。

代码说明:

在线上:47我们将年龄记录作为数组存储在变量中。作为一般规则,请始终用const声明变量,除非您知道该值会更改。

第49行:我们正在从数组中包装所有值,对于每个数组,我们正在运行一个循环函数,在each

下方解释了一个循环函数

第51行:我们将首先清除我们的搜索框(因为在第一次搜索之后,年龄文本停留在输入字段中),然后我们将从数组中键入AGE

由于我们存在存在的年龄值,并且不存在,因此我们需要分为两个条件的断言。

线:53:如果年龄等于77(表格中不存在年龄)

第54行:我们将断言第一行不包含这个年龄(对于此测试,我们只有一个或没有记录,因此足以仅将第一行检查为搜索结果)

第55行:我们还将检查未找到的行消息。

第56行:另一个条件(else) - 对于确实存在的年龄值,我们将运行其他主张,如下

第57行:我们将断言第一行包含搜索年龄

第58行:我们将断言表格中只有一行

测试4:检查添加新记录 - 不良代码练习

对于此测试案例,我们希望通过单击添加选项并提交新的记录模式来在表中提交新记录。我们还想检查记录是否正确添加到表中,并包含我们提供的所有值。我们有注意到这是一个不良的代码练习以及为何跳过测试的原因(xit)是因为有一种更好的方法来编写此代码以使用基本的JS概念更重复使用(我们将在此中看到这一点功能上的测试5与此相同,只是以更好的代码样式编写)。

代码说明:

第65行:我们将单击“添加”按钮

模态从第67行打开到第72行时,我们将填写所需的所有信息

第73行:我们将提交带有新记录信息的模式

第75行:我们将获得整个桌子主体

第76行:我们将找到包含我们记录的名字

的行

第77行:我们将从该行获取数据

第78-83行:我们将通过索引来包装第78-83行数据,并断言我们之前提供的正确值是在适当的列中写入的。如您所见,这里有很多代码重复,在步骤2中,在下一个测试中,我们将以较短,更好的方式编写它。

测试5:检查添加新记录

此测试将做与以前的测试完全相同的事情,但是我们将其组织为重复性较低且更容易维护的代码。

出于此测试的目的,我们将以固定装置的形式存储新用户数据,以便如果需要,我们可以轻松找到并编辑此数据。让我们在fixtures文件夹下创建一个新的users.json文件,然后插入以下数据:

{
  "user1": {
    "firstName": "Harvey",
    "lastName": "Specter",
    "age": "40",
    "userEmail": "specter@example.com",
    "salary": "700000",
    "department": "legal"
  }
}

代码说明:

与以前相同的行:89我们单击“添加”按钮以打开模态以添加新记录

在线上:90,我们正在调用我们的用户固定装置加载,然后我们正在为测试创建一个函数,我们将在其中使用此数据

第92行:我们正在创建一个新的变量columnIDs,对于此变量,我们将从固定文件中读取对象键的用户1数据(在这种情况下,对象键实际上是选择器ID的名称)

第93行:我们正在创建一个新的变量userData,对于此变量,我们将从固定文件中读取对象值以获取用户1数据(在这种情况下对象值是实际用户数据,我们将插入的模式中插入以添加新用户)

第95行:我们将扭曲所有列ID的选择器(我们的对象键)和每个选择器 - 下一步

第96行:我们将使用对象键作为选择器,获取它们,并从其对 - 用户对象值中键入用户数据值(这实际上是编写我们先前测试中从第67行中使用的内容的简短方法至72)。看看我们如何减少这里的代码并使未来更加可维护?

第98行:我们将单击模式中的提交按钮

第100行:我们要资产的记录每列包含我们为用户插入的值。所以,我们在这里得到整个桌子

第101行:我们正在找到包含用户名

的行

第102行:使用来自特定行的数据,我们正在创建一个函数以使断言

第103行:我们正在包装我们的用户数据(来自固定文件的对象值),对于每个用户数据),我们正在检索其值(来自灯具文件的实际值)和索引(0,1,2。 。)将作为下一步中的表列的索引

第104行:我们正在从行中包装整个数据,我们正在找到每一列的索引,并且我们声称它在固定文件文件中定义了正确的值。这基本上是我们从第78至8行中进行的测试中所做的。 在

Keys Values Entries
JS Objects

上传

上传功能使我们可以将文件提交给我们的Web应用程序。有一种简单的方法如何处理柏树。

让我们创建上传测试并进行解释。

1:安装用于上传文件的库,在vs终端中运行以下命令:

npm install cypress-file-upload

2:将此行添加到support/commands.js文件:

import 'cypress-file-upload';

3:首先创建一个新的测试文件,称为upload.cy.jse2e/elements_manipulation文件夹下。

4:在内部写下以下代码:

/// <reference types="Cypress" />

describe('Upload: Upload actions', () => {
  before('Navigate to upload page', () => {
    cy.visit('/upload-download');
  });

  it('Check upload action', () => {
    // Upload file
    cy.get('#uploadFile').attachFile('pic.png');
    // Assert that path message is displayed
    cy.get('#uploadedFilePath').should('have.text', `C:\\fakepath\\pic.png`);
  });
});

5:拍摄这张照片(下载它,右键单击,将图像保存为..),然后将其移至fixtures文件夹。重命名为:pic.png

Image to download

代码说明:

在线上:5我们访问了我们的应用程序,并且包含上传功能的页面

您会看到一个选择文件输入元素,因此我们要做的就是将图片上传到该元素。为此,我们将使用库中的attachFile()方法。您可以在线上看到:10我们正在获取输入ID选择器并在其上执行上传。我们还需要提供文件的确切名称和扩展名,在这种情况下为pic.png。赛普拉斯将在固定装置文件夹中自动寻找它。

这样做之后,我们的应用程序将为我们提供服务器上图片文件位置的文本,因此在这种情况下对我们来说 - 这是确认图像已上传的确认。我们想断言文本存在,因此在线:12我们正在编写断言,以确认我们的文本元素包含具有正确路径的文本。

家庭作业:

为表(搜索,排序,添加/删除记录等)编写更多方案。访问课程中提供的链接,学习和理解JS中的对象和对象方法。

不要忘记推动您今天在github-记得git命令上所做的一切吗?

git add .

git commit -am "add: table, upload tests"

git push

在第8课!

中见

Completed code for this lesson

如果您学到了新知识,请随时购买咖啡来支持我的工作。

Buy Me A Coffee