本篇博客将给大家介绍如何在UI层自动化测试中管理配置信息以及对敏感数据进行脱敏处理。为了完成此次课程目标,拆分了2分task。
- 配置信息管理
- 敏感信息脱敏处理
配置信息管理
在讲解如何管理信息前,让我们先来思考这几个问题,哪些配置信息需要管理?为什么要管理配置信息?如何管理配置信息?
对于第一个问题,首先,我们可能会想到Cypress自定义的配置信息,例如设置是否失败时生成测试截图,这一类配置信息是Cypress自定义的,在前面的课程中也介绍过几个使用过的配置信息,此类配置信息在cypress.json中进行设置即可。除此之外,还有一类信息和被测应用相关的,例如,被测应用的baseUrl。故这里讨论的配置信息总共两类,第一类Cypess自带的配置信息,第二类需要用户自己管理的配置信息,即被测应用的baseUrl,数据库连接信息等。
回答了第一个问题,接着你可能会问,baseUrl多简单啊,为什么需要单独管理呢?因为一套自动化脚本需要多个环境切换运行,不同的环境baseUrl肯定不一样,所以需要根据被测环境使用对应环境的baseUrl,这样切换环境后脚本仍能正常运行。那么如何根据环境管理baseUrl呢?这就是本次课程的内容。
项目初始化成功后,会在代码根目录下自动生成cypress.json文件,一些Cypress自定义的配置信息都可以在该文件中进行设置。例如前面课程使用过的“screenshotOnRunFailure”,当设置为true时,用例运行失败时会自动生成运行截图,当设置为false时,用例运行失败时不会自动生成截图。Cypress框架支持环境变量设置,给配置信息管理带来了很大的便捷。实际项目中可以借助cypress.json文件管理配置信息。
假设被测应用有2个环境DEV和ST环境,DEV环境的baseUrl是 “https://angular.realworld.io/”, ST环境的baseUrl是“https://angular-st.realworld.io/”, 可以看到ST环境的baseUrl实际是个错误的Url,后面我们会演示当切换到DEV环境时,脚本能正常运行,当切换到ST环境时运行会失败,说明确实根据环境值使用了不同的baseUrl。
为了实现根据环境使用不同的baseUrl,首先在cypress.json文件中定义如下内容.
{
"defaultCommandTimeOut": 9000,
//默认等待时间设置
"viewportHeight": 1080, //运行用例时页面大小设置
"viewportWidth": 1920,
"video": false,
//运行测试脚本时不录屏
"screenshotOnRunFailure": false,
//脚本运行失败后不进行截图
"testFiles": "**/*_spec.js",
//测试脚本匹配规则
"reporter": "cypress-multi-reporters",
//为了生成美观的测试报告
"reporterOptions": {
"configFile": "reporter.json"
},
//以上都是cypress自带的配置信息,前面的课程中有涉及,对于自带的配置信息,这里不再过多的介绍
"env" : {
//Cypress原生支持环境变量设置
"appEnv": "dev",
//这里设置的环境变量名称是“appEnv”,环境变量值是“dev”
"baseUrl": {
"dev": "https://angular.realworld.io/",
"st": "https://angular-st.realworld.io/"
}
}
}
因为Cypress原生支持环境变量的设置,即在cypress.json文件中设置环境变量名称和环境变量值。设置的方式如上所示“env” :{......},设置好后,即可在脚本中读取环境变量的值。获取baseUrl值的方式是Cypress.env('baseUrl')['dev'或者'st']。
调用getBaseUrl()的脚本如下所示,当cypress.json文件中appEnv=“dev”时,运行该脚本能打开一个真实的应用。修改cypress.json文件中的appEnv=“st”时,再次运行该脚本会失败,因为使用的baseUrl是错误的baseUrl。
Test Runner中选择“readBaseUrl_spec.js”文件即可运行下面的脚本。
function getBaseUrl() {
return Cypress.env('baseUrl')[Cypress.env('appEnv')||'dev']
//根据appEnv的值,获取不同的baseUrl值
}
it("show how to manage configuration", () => {
console.log("this baseUrl is " + getBaseUrl())
cy.visit(getBaseUrl())
//访问获取到的baseUrl应用
});
实际项目中,当自动化脚本在持续集成平台上运行时,例如jenkins上运行,还可以通过设置环境变量的方式来修改appEnv的值。即在jenkins的的job中设置脚本为“export appEnv=‘dev’或者appEnv=‘st’”来控制切换到哪个环境运行自动化脚本。这样自动化脚本在jenkins上运行时,无需修改任何测试脚本,即可完成环境的自动化切换以及脚本运行。在后续的“Jenkins上运行自动化脚本”章节会给大家详细演示如何在jenkins上运行自动化脚本。
上面只管理了baseUrl配置信息,如果UI测试中需要操作数据库准备测试数据,那么还需要管理数据库连接信息。当管理多个配置信息时,可以在cypress.json中写入所有需要管理的配置信息。内容如下所示
"env": {
"RETRIES": 2,
"appEnv": "dev",
"baseUrl": {
"dev": "https://angular.realworld.io",
"st": "https://angular-st.realworld.io"
},
//假设下面是要管理的不同环境的所有配置信息
"configs": {
"dev": {
"baseUrl": "https://angular.realworld.io",
"mysql": {
"host": "localhost",
"user": "root",
"password": "root123456",
"data": "test"
},
"st": {
"baseUrl": "https://angular-st.realworld.io",
"mysql": {
"host": "localhost",
"user": "root",
"password": "root123456",
"data": "test"
}
}
}
}
}
读取configs中值的脚本如下所示,同样,Test Runner中选择“readConfigs_spec.js”文件即可运行下面的脚本,运行的时候打开浏览器的console,能看到获取到的configs信息。
function getConfig() {
return Cypress.env('configs')
//返回所有的configs信息
}
it("show how to manage configuration", () => {
console.log(JSON.parse(JSON.stringify(getConfig())).dev.baseUrl);
console.log(JSON.parse(JSON.stringify(getConfig())).dev.mysql.user);
// 读取dev环境的baseUrl和mysql的user信息
});
除了封装方法获取配置信息外,还可以通过plugin目录下index.js文件直接获取配置信息。前面介绍过如果需要连接数据库,需要在index.js文件中编写自定义Task,下面脚本创建数据库连接时直接从config对象中获取数据库连接信息,这样连接数据库的部分也可以根据环境自动切换了。
const dbConfig=config.env.configs[config.env.appEnv];
const dbConnection = mysql.createConnection({
host: dbConfig.mysql.host,
user: dbConfig.mysql.user,
password: dbConfig.mysql.password,
database: dbConfig.mysql.dataBase
});
//从config对象中获取数据库连接信息
on('task', {
queryTestDB(sql) {
return new Promise((resolve, reject) => {
dbConnection.query(sql, function (err, result) {
if (err) {
reject(err)
} else {
return resolve(result)
}
})
})
}
});
//封装执行sql的task,该task中使用dbConnection
Test Runner上选择“readMysqlConfig_spec.js”文件,该文件中的脚本调用了“queryTestDB”Task获取数据库中数据,执行脚本后,在浏览器console上能看到读取到的数据库表信息,说明整个数据查询过程正确。
上面演示了如何管理配置信息,这里再总结下配置信息管理的要点。
- 与环境相关的信息都需要存入配置文件中进行管理,例如被测应用的baseUrl或者数据库连接信息等,这样能保证CICD平台上通过设置环境值切换到不同环境下运行自动化案例。
- 创建configManage文件,封装方法获取配置文件中的信息,这样,当测试脚本中任何地方需要配置信息时,调用configManage.functionxxx()即可。
- 如果选用cypress框架,无需创建单独的json格式配置文件,可以直接把配置信息存放到cypress.json文件中。
- 如果选择cypress框架,index.js文件中可直接获取配置信息,无需调用configManage中封装的方法,这样更简单。
敏感信息脱敏处理
下面内容重点给大家介绍UI层自动化测试中如何进行脱敏,例如测试数据管理文件中存放了登陆用户的密码,那么需要对密码信息进行加密处理后放到测试数据管理文件中,在测试场景的登陆步骤,从测试文件中获取到加密后的密码后进行解密,然后在页面上输入解密的密码。这里采用了“crypto-js”包进行加密和解密处理,如果只是解决UI层自动化测试的脱敏处理,只需对内容加密以及解密即可,无需关注加密、解密算法细节的,如果想了解加密和解密算法细节,请Google检索“crypto-js”的内容。另外,每个项目可能会采用自己的加密算法,本次课程主要给大家讲解脱敏处理的思路,在实际项目中,大家可以采用自己项目约定的加密算法进行脱敏处理。
首先,执行命令“npm install crypto-js",执行该命令后自动下载“crypto-js”包,并在package.json文件中添加下载该依赖的配置信息。
创建secret.js文件,里面实现对明文进行加密的function和对密文进行解密的function,值得注意的是密钥的长度,由于对称解密使用的算法是 AES-128-CBC算法,数据采用 PKCS#7 填充 , 因此这里的 key 需要为16位。代码内容如下所示
import CryptoJS from 'crypto-js' //引入下载的crypto-js包
const key = CryptoJS.enc.Utf8.parse('1234123412ABCDEF'); //十六位十六进制数作为密钥
const iv = CryptoJS.enc.Utf8.parse('ABCDEF1234123412'); //十六位十六进制数作为密钥偏移量
function decrypt(encryptString) { //对密文进行解密处理,调用该方法传入密文会返回解密后的明文信息
let decrypt = CryptoJS.AES.decrypt(encryptString, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypt.toString(CryptoJS.enc.Utf8);
}
function encrypt(plaintText) { //对明文进行加密处理,调用该方法传入明文信息会返回密文信息
let encrypt = CryptoJS.AES.encrypt(plaintText, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypt.toString()
}
module.exports = { //export出上面的两个方法,方面其他js文件调用这两个方法
decrypt: decrypt,
encrypt: encrypt
};
编写好上述内容后,为了方便在Cypress的Test Runner上调试,再创建文件“secret_spec.js”,该文件中输入前面课程使用的明文密码“12345678”进行加密处理,并把加密后的密文进行解密处理,以此验证解密后的内容是否正确,代码详细信息如下所示
const secret = require('./secret'); //引入secret.js,这样采用调用该文件中的function
it ('should encrypt and decrypt correctly',() => {
console.log(secret.encrypt('12345678')); //对明文进行加密处理
console.log(secret.decrypt('C4xAhY/0Dv8noBcZRtE8Mg==')); //对密文进行解密处理
});
Test Runner上选择“secret_spec.js”脚本即可运行上面的案例,运行结果如下图所示,可以看到解密后的明文是“12345678”说明加密和解密过程正确。
接着,在fixtures目录下的st和dev目录中创建encryptUsers.json文件,里面存放加密后的密码。
然后,修改“测试数据管理”课程中创建的“manageTestData.json”文件,添加读取加密user信息的方法。
function getDifEnvTestData(fileName,testDataAlias) {
cy.fixture('testData/'+Cypress.env('appEnv')+'/'+fileName).as(testDataAlias) //通过环境变量指定的环境值,读取不同环境下的文件信息
}
function getCommonTestData(fileName,testDataAlias) {
cy.fixture('testData/'+fileName).as(testDataAlias)
}
function getUserTestData(testDataAlias) {
getCommonTestData('user.json',testDataAlias)
}
function getEncryptUser(testDataAlias) {
getDifEnvTestData('encryptUser.json',testDataAlias) //获取不同环境下的加密后的用户信息
}
module.exports= {
getUserTestData: getUserTestData,
getEncryptUser:getEncryptUser};
最后,编写一个新的登陆测试场景脚本,在登陆测试场景中获取加密后的用户信息,解密处理后再用解密后的名文信息进行登陆。
const secret = require('./secret');
const configManage=require('./configManagePage');
const testDataManage = require('../file/manageTestData');
it('should login successfully', () => {
testDataManage.getEncryptUser('encryptUsers');
cy.visit(configManage.getBaseUrl());
cy.get('app-layout-header ul li a[href="/login"]').click();
cy.get('@encryptUsers').then((data) => {
cy.get('[formcontrolname=email]').type(data.regular.name);
cy.get('[formcontrolname=password]').type(secret.decrypt(data.regular.password));
});
cy.get('app-auth-page button[type="submit"]').click();
});
Test Runner上选择“loginWithEncryptMessage_spec.js”即可运行上面的脚本,运行结果如下所示,可以看到从测试数据文件中读取到的密码是密文,在应用的登陆页面输入的是解密后的明文,脚本执行完成后登陆成功,说明整个加密、解密过程正确。
接下来,总结下完成脱敏处理的三个步骤。
- 第一:引入一个对称加密算法,例如案例中使用的“crypto-js”,编写加密、解密方法。
- 第二:把测试文件中的敏感数据修改为密文。
- 第三:需要使用这些数据的地方调用解密方法,解密后供测试场景使用。