skyline/docs/zh/test/3-1-E2E-form-operation.md
zhangjingwei 91c5b7933b feat: udpate table header
1. update table header's buttons postion
2. update e2e command to adjust the change

Change-Id: I50379428eaad9ba3b4571987b2478e6f05d25caa
2023-11-17 17:22:35 +08:00

592 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

简体中文 | [English](../../en/test/3-1-E2E-form-operation.md)
因为前端框架使用的一致性,我们在编写表单操作的相关用例,选取元素并进行操作时,往往会发现有很强的规律性,所以我们对大多数表单操作都编写了相应的 Cypress 函数,极大的减少了编写测试用例的难度,以下会对主要使用的表单操作函数做出详细的说明。
> 注意:编写的函数均以能完整完成对一个表单项的操作为原则
## 点击按钮的操作
- `closeNotice`
- 关闭操作后右上角的操作成功的提示信息
![notice](images/e2e/form/notice.png)
- `waitFormLoading`
- 等待表单请求完成
- 表单填写并验证通过后,点击确认按钮,会向服务端发起相应请求,这时表单项的确认按钮会处于`Loading`的状态
- 使用该函数,而不是`cy.wait(seconds)`,能更有效的保证同步请求已经处理完全,从而保证后续用例的先决条件
![wait-form-loading](images/e2e/form/wait-form-loading.png)
- `clickFormActionSubmitButton`
- 点击确认型表单的确认按钮,并等待请求完成
![click-form-submit](images/e2e/form/click-form-submit.png)
- `clickModalActionSubmitButton`
- 点击弹窗型表单的确认按钮,并等待请求完成
![click-modal-submit](images/e2e/form/click-modal-submit.png)
- `clickModalActionCancelButton`
- 点击弹窗型表单的取消按钮
- `clickConfirmActionSubmitButton`
- 点击确认型表单的确认按钮,等待请求完成,并关闭请求成功的提示信息
- 参数`waitTime`,关闭提示信息后的等待时间
![click-confirm-submit](images/e2e/form/click-confirm-submit.png)
- `checkDisableAction`
- 某些数据不符合要求时,使用批量操作,会弹出报错,该函数验证该数据的确不合操作要求,并关闭报错提示
- 以锁定状态的云主机`test/e2e/integration/pages/compute/instance.spec.js`为例
- 锁定后不再支持启动、关闭、重启操作
```javascript
it('successfully lock', () => {
cy.tableSearchText(name)
.clickConfirmActionInMoreSub('Lock', 'Instance Status')
.wait(10000);
cy.tableSearchText(name)
.selectFirst()
.clickHeaderActionButtonByTitle('Start')
.checkDisableAction(2000)
.clickHeaderActionButtonByTitle('Stop')
.checkDisableAction(2000)
.clickHeaderActionButtonByTitle('Reboot')
.checkDisableAction(2000);
});
```
![disable-action](images/e2e/form/disable-action.png)
- `clickStepActionNextButton`
- 点击分步表单的下一步/确认按钮
- 以创建云主机用例`test/e2e/integration/pages/compute/instance.spec.js`为例
- 共需要点击 3 次下一步1 次确认按钮
```javascript
it('successfully create', () => {
cy.clickHeaderActionButton(0)
.url()
.should('include', `${listUrl}/create`)
.wait(5000)
.formTableSelect('flavor')
.formTableSelect('image')
.formSelect('systemDisk')
.formAddSelectAdd('dataDisk')
.formSelect('dataDisk')
.wait(2000)
.clickStepActionNextButton()
.wait(5000)
.formTableSelectBySearch('networkSelect', networkName, 5000)
.formTableSelectBySearch('securityGroup', 'default', 5000)
.wait(2000)
.clickStepActionNextButton()
.formInput('name', name)
.formRadioChoose('loginType', 1)
.formInput('password', password)
.formInput('confirmPassword', password)
.wait(2000)
.clickStepActionNextButton()
.wait(2000)
.clickStepActionNextButton()
.waitFormLoading()
.url()
.should('include', listUrl)
.closeNotice()
.waitStatusActiveByRefresh();
});
```
![click-step-next](images/e2e/form/click-step-next.png)
- `clickStepActionCancelButton`
- 点击分步表单的取消按钮
- 以镜像创建云主机用例`test/e2e/integration/pages/compute/image.spec.js`为例
- 只验证能成功进入到创建云主机页面,然后点击取消按钮完成该用例
```javascript
it('successfully create instance with cancel', () => {
cy.tableSearchText(name)
.clickActionInMore('Create Instance')
.wait(2000)
.clickStepActionCancelButton();
});
```
## 对表单项的操作
通过页面查看元素的结构、样式等,发现,所有的表单项,都具有`id`,而且对应于开发时编写的表单配置`formItem`的`name`属性,也可直接通过查看页面内元素的`id`获取`name`,如下图所示,`form-item-col-`之后的内容便是`name`
![form-name](images/e2e/form/form-name.png)
- `formInput`
- 带有`input`输入框的表单项输入内容
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`value`,输入的内容
- 以编辑云主机用例`test/e2e/integration/pages/compute/instance.spec.js`为例
```javascript
it('successfully edit', () => {
cy.tableSearchText(name)
.clickActionInMore('Edit')
.formInput('name', newname)
.clickModalActionSubmitButton()
.wait(2000);
});
```
![input](images/e2e/form/input.png)
- `formJsonInput`
- 带有`textarea`输入框的表单项输入`json`格式内容
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`content`,输入的对象
- 以创建堆栈,编写`json`型的参数`test/e2e/integration/pages/heat/stack.spec.js`为例
```javascript
it('successfully create', () => {
const volumeJson = {
name: volumeName,
};
cy.clickHeaderActionButton(0, 2000)
.formAttachFile('content', contentFile)
.formAttachFile('params', paramFile)
.clickStepActionNextButton()
.wait(2000)
.formInput('name', name)
.formJsonInput('volume_name_spec', volumeJson)
.clickStepActionNextButton()
.waitFormLoading()
.wait(5000)
.tableSearchSelectText('Name', name)
.waitStatusActiveByRefresh();
});
```
![textarea-json](images/e2e/form/textarea-json.png)
- `formCheckboxClick`
- 点击表单项中的`checkbox`
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`index`,默认为`0`
- 以云主机修改配置`test/e2e/integration/pages/compute/instance.spec.js`为例
```javascript
it('successfully resize', () => {
cy.tableSearchText(name)
.clickActionInMoreSub('Resize', 'Configuration Update')
.wait(5000)
.formTableSelect('newFlavor')
.formCheckboxClick('option')
.clickModalActionSubmitButton()
.waitStatusActiveByRefresh();
});
```
![checkbox](images/e2e/form/checkbox.png)
- `formTableSelectAll`
- 对表格选择类型的表单项做全选操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 以云硬盘类型修改访问`test/e2e/integration/pages/storage/volume-type.spec.js`为例
```javascript
it('successfully manage access to projects', () => {
cy.tableSearchText(name)
.clickActionInMore('Manage Access')
.formCheckboxClick('isPublic')
.formTableSelectAll('access')
.clickModalActionSubmitButton();
});
```
![select-all](images/e2e/form/select-all.png)
- `formTableNotSelectAll`
- 对表格选择类型的表单项做取消全选操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 以主机集合管理主机时不选择主机`test/e2e/integration/pages/compute/aggregate.spec.js`为例
```javascript
it('successfully manage host: no host', () => {
cy.tableSearchText(newname)
.clickActionInMore('Manage Host')
.formTableNotSelectAll('hosts')
.clickModalActionSubmitButton();
});
```
![unselect-all](images/e2e/form/unselect-all.png)
- `formTableSelect`
- 对表格选择类型的表单项做选择操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`value`,如果设置`value`,则选择表格中含有该值的条目,如果不设置`value`,则选择表格中的第一个条目
- 以云主机挂载网卡选择网络`test/e2e/integration/pages/compute/instance.spec.js`为例
```javascript
it('successfully attach interface', () => {
cy.tableSearchText(name)
.clickActionInMoreSub('Attach Interface', 'Related Resources')
.wait(5000)
.formTableSelect('network')
.clickModalActionSubmitButton();
});
```
![select-table](images/e2e/form/select-table.png)
- `formTableSelectBySearch`
- 对表格选择类型的表单项,先做搜索操作,然后选择条目中的第一条
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`value`,搜索内容,一般是对搜索项中`名称`的搜索
- 参数`waitTime`,搜索后等待时间,不设置,等待 2 秒钟
- 以云主机挂载云硬盘选择云硬盘`test/e2e/integration/pages/compute/instance.spec.js`为例
- 操作成功后,进入云硬盘列表页查看云硬盘的状态为“已使用”
```javascript
it('successfully attach volume', () => {
// prepare volume
cy.visitPage(listUrl)
.tableSearchText(name)
.clickActionInMoreSub('Attach Volume', 'Related Resources')
.wait(5000)
.formTableSelectBySearch('volume', volumeName)
.clickModalActionSubmitButton()
.wait(5000);
// check attach successful
cy.tableSearchText(name)
.goToDetail()
.clickDetailTab('Volumes')
.tableSearchText(volumeName)
.checkColumnValue(2, 'In-use');
});
```
![select-table-search](images/e2e/form/select-table-search.png)
- `formTableSelectBySearchOption`
- 对表格选择类型的表单项,先做搜索操作,然后选择条目中的第一条
- 搜索是对搜索项的选择,不同于`formTableSelectBySearch`是基于输入
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`name`,搜索项的名称
- 参数`value`,搜索项对应的值
- 参数`waitTime`,搜索后的等待时间,默认为 2 秒
- 以创建全量备份`test/e2e/integration/pages/storage/backup.spec.js`为例
- 选择状态为使用中的云硬盘
```javascript
it('successfully create full backup', () => {
cy.clickHeaderActionButton(0, 5000)
.formInput('name', name)
.formTableSelectBySearch('volume', volumeName)
.clickModalActionSubmitButton()
.wait(5000)
.waitTableLoading();
cy.clickHeaderActionButton(0, 5000)
.formInput('name', nameIns)
.formTableSelectBySearchOption('volume', 'Status', 'In-use')
.clickModalActionSubmitButton();
cy.wait(30000);
});
```
![select-table-option](images/e2e/form/select-table-option.png)
- `formSelect`
- 对选择器类型的表单项的操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`label`,选中的内容,如果不设置,选中第一个选项,如果设置,选择`label`对应的选项
- 以创建云主机组选择策略`test/e2e/integration/pages/compute/server-group.spec.js`为例
```javascript
it('successfully create', () => {
cy.clickHeaderActionButton(0)
.formInput('name', name)
.formSelect('policy')
.clickModalActionSubmitButton();
});
```
![select](images/e2e/form/select.png)
- 以网络 QoS 策略创建带宽限制规则时设置方向为“入方向”`test/e2e/integration/pages/network/qos-policy.spec.js`为例
```javascript
it('successfully create bandwidth ingress limit rule', () => {
cy.tableSearchText(name)
.clickActionInMore('Create Bandwidth Limit Rule')
.formSelect('direction', 'ingress')
.clickModalActionSubmitButton();
});
```
![select-value](images/e2e/form/select-value.png)
- `formRadioChoose`
- 对单选类型的表单项的操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`itemIndex`,选中第几项,默认为 0即选择第一项
- 以创建密钥选择“导入密钥”`test/e2e/integration/pages/compute/keypair.spec.js`为例
```javascript
it('successfully create by file', () => {
cy.clickHeaderActionButton(0)
.formRadioChoose('type', 1)
.formInput('name', nameByFile)
.formAttachFile('public_key', filename)
.clickModalActionSubmitButton()
.tableSearchText(nameByFile)
.checkTableFirstRow(nameByFile);
});
```
![radio](images/e2e/form/radio.png)
- `formAttachFile`
- 对上传文件的表单项的操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`filename`,上传文件的名称,文件需要预先保存在`test/e2e/fixtures`目录下
- 以创建密钥选择文件为例`test/e2e/integration/pages/compute/keypair.spec.js`为例
```javascript
it('successfully create by file', () => {
cy.clickHeaderActionButton(0)
.formRadioChoose('type', 1)
.formInput('name', nameByFile)
.formAttachFile('public_key', filename)
.clickModalActionSubmitButton()
.tableSearchText(nameByFile)
.checkTableFirstRow(nameByFile);
});
```
![attach-file](images/e2e/form/attach-file.png)
- 以创建镜像选择文件为例`test/e2e/integration/pages/compute/image.spec.js`为例
```javascript
it('successfully create', () => {
cy.clickHeaderActionButton(0)
.url()
.should('include', `${listUrl}/create`)
.formInput('name', name)
.formAttachFile('file', filename)
.formSelect('disk_format', 'QCOW2 - QEMU Emulator')
.formSelect('os_distro', 'Others')
.formInput('os_version', 'cirros-0.4.0-x86_64')
.formInput('os_admin_user', 'root')
.formSelect('usage_type', 'Common Server')
.formText('description', name)
.clickFormActionSubmitButton()
.wait(2000)
.url()
.should('include', listUrl)
.tableSearchText(name)
.waitStatusActiveByRefresh();
});
```
![attach-file-image](images/e2e/form/attach-file-image.png)
- `formAddSelectAdd`
- 对可增加条目的表单项的操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 以主机集合管理元数据时,添加新的自定义元数据`test/e2e/integration/pages/compute/aggregate.spec.js`为例
```javascript
it('successfully manage metadata', () => {
cy.tableSearchText(name)
.clickActionInMore('Manage Metadata')
.wait(5000)
.formAddSelectAdd('customs')
.formInputKeyValue('customs', 'key', 'value')
.formTransferLeftCheck('systems', 0)
.clickModalActionSubmitButton();
});
```
![add-select](images/e2e/form/add-select.png)
- `formSwitch`
- 对开关型的表单项的点击操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 以创建具有共享属性的网络 QoS 策略`test/e2e/integration/pages/network/qos-policy.spec.js`为例
```javascript
it('successfully create', () => {
cy.clickHeaderActionButton(0)
.wait(2000)
.formInput('name', name)
.formText('description', name)
.formSwitch('shared')
.clickModalActionSubmitButton();
});
```
![switch](images/e2e/form/switch.png)
- `formButtonClick`
- 对表单项中的按钮的点击操作
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 以项目更改配额时展开/关闭“高级选项”`test/e2e/integration/pages/identity/project.spec.js`为例
```javascript
it('successfully edit quota', () => {
cy.tableSearchText(name)
.clickActionInMore('Edit Quota')
.formInput('instances', 11)
.formButtonClick('more')
.wait(2000)
.formButtonClick('more')
.clickModalActionSubmitButton();
});
```
![more](images/e2e/form/more.png)
![more-open](images/e2e/form/more-open.png)
- `formTransfer`
- 对穿梭框类型的表单操作
1. 对左侧的穿梭框基于搜索展示指定待选条目
2. 选中待选条目的第一条
3. 点击穿梭框中间的方向按钮,使得选中内容进入到右侧穿梭框中
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`value`,搜索内容
- 以项目管理用户`test/e2e/integration/pages/identity/project.spec.js`为例
```javascript
it('successfully manage user', () => {
cy.tableSearchText(name)
.clickActionInMore('Manage User')
.formTransfer('select_user', username)
.formTransferRight('select_user', username)
.formSelect('select_user', 'admin')
.clickModalActionSubmitButton();
});
```
![transfer-left](images/e2e/form/transfer-left.png)
- `formTransferRight`
- 对右侧的穿梭框基于搜索展示指定待选条目
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`value`,搜索内容
- 以用户组管理用户为例`test/e2e/integration/pages/identity/user-group.spec.js`为例
```javascript
it('successfully manage user', () => {
cy.tableSearchText(name)
.clickActionInMore('Manage User')
.formTransfer('select_user', username)
.formTransferRight('select_user', username)
.clickModalActionSubmitButton();
cy.tableSearchText(name)
.goToDetail()
.clickDetailTab('Sub Users', 'userGroup');
});
```
![transfer-right](images/e2e/form/transfer-right.png)
- `formTabClick`
- 点击带有 Tab 的表单项中的 Tab
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`index`,指定的 Tab 的下标
- 以编辑浮动 IP切换到共享策略为例`test/e2e/integration/pages/network/floatingip.spec.js`为例
```javascript
it('successfully edit', () => {
cy.clickFirstActionButton()
.formText('description', 'description')
.formTabClick('qos_policy_id', 1)
.wait(5000)
.formTableSelectBySearch('qos_policy_id', policyName)
.clickModalActionSubmitButton()
.wait(2000);
});
```
![tab](images/e2e/form/tab.png)
- `formInputKeyValue`
- 对`KeyValue`组件的表单项进行输入操作,一般是配合`formAddSelectAdd`使用,对添加的新的`KeyValue`组件的条目,输入内容
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`key`,左侧 input 输入的内容
- 参数`value`,右侧 input 输入的内容
- 以主机集合管理元数据时,添加新的自定义元数据`test/e2e/integration/pages/compute/aggregate.spec.js`为例
```javascript
it('successfully manage metadata', () => {
cy.tableSearchText(name)
.clickActionInMore('Manage Metadata')
.wait(5000)
.formAddSelectAdd('customs')
.formInputKeyValue('customs', 'key', 'value')
.formTransferLeftCheck('systems', 0)
.clickModalActionSubmitButton();
});
```
![key-value](images/e2e/form/key-value.png)
- `formTransferLeftCheck`
- 对左侧的穿梭框的操作
1. 选中左侧穿梭框中的指定节点
2. 点击穿梭框中间的方向按钮,使得选中内容进入到右侧穿梭框中
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`index`,节点的下标
- 以主机集合管理元数据时,添加新的自定义元数据`test/e2e/integration/pages/compute/aggregate.spec.js`为例
```javascript
it('successfully manage metadata', () => {
cy.tableSearchText(name)
.clickActionInMore('Manage Metadata')
.wait(5000)
.formAddSelectAdd('customs')
.formInputKeyValue('customs', 'key', 'value')
.formTransferLeftCheck('systems', 0)
.clickModalActionSubmitButton();
});
```
![transfer-left-click](images/e2e/form/transfer-left-click.png)
- `formTransferRightCheck`
- 对右侧的穿梭框的操作
1. 选中右侧穿梭框中的节点
2. 点击穿梭框中间的方向按钮,使得选中内容进入到左侧穿梭框中
- 参数`formItemName`,即开发代码中`formItem`的`name`值
- 参数`index`,节点的下标
- 以云主机类型,修改元数据`test/e2e/integration/pages/compute/flavor.spec.js`为例
```javascript
it('successfully manage metadata', () => {
cy.clickTab('Custom')
.tableSearchText(customName)
.clickActionButtonByTitle('Manage Metadata')
.wait(5000)
.formTransferLeftCheck('systems', 0)
.clickModalActionSubmitButton();
// todo: remove key-value metadata
cy.clickTab('Custom')
.tableSearchText(customName)
.clickActionButtonByTitle('Manage Metadata')
.wait(5000)
.formTransferRightCheck('systems', 0)
.clickModalActionSubmitButton();
});
```
![transfer-right-check](images/e2e/form/transfer-right-check.png)
对资源操作的各种操作,主要用到了上方介绍的函数,函数的具体编写,请查看`test/e2e/support/form-commands.js`