• 4.8 playwright实现页面及接口的自动化


    1、jest.config.js

    1. /*
    2. * For a detailed explanation regarding each configuration property, visit:
    3. * https://jestjs.io/docs/configuration
    4. */
    5. module.exports = {
    6. "reporters": [
    7. "default",
    8. ["jest-html-reporters", {
    9. "publicPath": "./html-report",
    10. "filename": `${new Date().getTime()}.html`,
    11. "openReport": false
    12. }]
    13. ]
    14. };

    2、src/case/page

    login_page.js

    1. /**
    2. * @description steps表示每个用例的执行步骤,selector字段使用的是playwright的selector选择器
    3. * @description assertElement表示需要断言的元素,selector可以直接使用浏览器copy的selector
    4. */
    5. module.exports = {
    6. pageName: 'login-page',
    7. url: 'https://192.168.3.80:8080/user/login',
    8. steps: [
    9. {
    10. type: 'input',
    11. selector: 'text=账号',
    12. value: 'user'
    13. },
    14. {
    15. type: 'input',
    16. selector: 'text=密码',
    17. value: 'Wlh@1234567'
    18. },
    19. {
    20. type: 'click',
    21. selector: 'button',
    22. }
    23. ],
    24. assertElement: [
    25. {
    26. selector: '#root > div > div > div:nth-child(1) > div > h1 > span',
    27. value: 'test信息平台'
    28. },
    29. {
    30. selector: '#root > div > div > div:nth-child(1) > div > form > div:nth-child(3) > button > span',
    31. value: '登录'
    32. }
    33. ]
    34. }

    home_page.js

    1. module.exports = {
    2. pageName: 'home-page',
    3. url: 'https://192.168.3.80:8080/home',
    4. steps: [],
    5. assertElement: [
    6. {
    7. selector: '#logo > a > h1',
    8. value: 'test信息平台'
    9. },
    10. {
    11. selector: '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(4) > div > div > div > div._2Wj8-Ajffvav9sQz3y4esS > span',
    12. value: '近30天趋势'
    13. },
    14. {
    15. selector: '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(5) > div > div > div > div._2Wj8-Ajffvav9sQz3y4esS > span',
    16. value: '年度趋势'
    17. },
    18. {
    19. selector: '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(6) > div > div > div > div._2Wj8-Ajffvav9sQz3y4esS > span',
    20. value: '结果占比'
    21. },
    22. {
    23. selector: '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(7) > div > div > div > div._2Wj8-Ajffvav9sQz3y4esS > span',
    24. value: '任务占比'
    25. },
    26. {
    27. selector: '#root > div > div > div._1wGopWCKE7xbN00hj3CcxY.ant-layout > div._1Z92MrfcbggFRHWL0Eku7O.ant-layout-header > div > span._1r_Ku5UxCqCBFcxYwa7YcI.ant-dropdown-trigger > span.TPdRrLa458rcF0shFyj7D > span._1DLXYUJQFelazd_39MSHRs > span',
    28. value: '已登录'
    29. }
    30. ]
    31. }

     keyword_page.js

    1. module.exports = {
    2. pageName: 'keyword-page',
    3. url: 'https://192.168.3.80:8080/keyword/list',
    4. steps: [],
    5. assertElement: [
    6. {
    7. selector: '#content > div > div > div > h3',
    8. value: 'xxx管理'
    9. },
    10. {
    11. selector: '#content > div > div > div > div > div > div._2z6IDqY1I9YYJ78A_TdGrW > div > div > div._3ICFjh4oC0gh_vBuZ4fsMO > button.ant-btn.my-button.ant-btn-primary.ant-btn-background-ghost > span',
    12. value: '重置'
    13. },
    14. {
    15. selector: '#content > div > div > div > div > div > div:nth-child(2) > div > div.ant-spin-nested-loading > div > div > div:nth-child(1) > div > div > div > div.qZHpEC_JchvC5swxxm4uE > button > span',
    16. value: '新建xxx策略'
    17. }
    18. ]
    19. }

    3、src/config/setting.js

    1. /**
    2. * @description projectName表示用例集的名称
    3. * @description tempLogPath表示请求失败日志存放的地方
    4. * @type {{tempLogPath: string, projectName: string}}
    5. */
    6. module.exports = {
    7. projectName: 'test信息平台',
    8. tempLogPath:'/log/request.log'
    9. }
    10. /**
    11. * @description orderList表示任务运行时的执行顺序
    12. */
    13. module.exports.orderList =[
    14. 'login_page',
    15. 'home_page',
    16. 'keyword_page'
    17. ]

    4、src/log/request.log

    5、src/test/test.js

    1. const setting = require('../config/setting')
    2. const {chromium} = require('playwright');
    3. const file_load = require('../utils/file_load')
    4. const assertElement = require('../utils/assert_element')
    5. const path = require('path')
    6. const temp_log = require('../utils/file_temp_log')
    7. jest.setTimeout(10000)
    8. describe(`${setting.projectName}`, function () {
    9. let browser
    10. let page
    11. let case_path = 'src/case/page'
    12. let files = file_load(case_path)
    13. beforeAll(async function () {
    14. browser = await chromium.launch({
    15. headless: false, slowMo: 300, args: ['--start-maximized']
    16. });
    17. page = await browser.newPage({viewport: null, ignoreHTTPSErrors: true});
    18. page.on('requestfailed', function (request) {
    19. let obj = {
    20. time: new Date().toLocaleString(),
    21. url: request.url(),
    22. method: request.method(),
    23. // headers: request.allHeaders(),//这个方法用来取代下面的request.headers(),但是实际上没有生效
    24. headers: request.headers(),
    25. data: request.postData(),
    26. }
    27. temp_log(setting.tempLogPath, JSON.stringify(obj))
    28. })
    29. })
    30. beforeEach(async () => {
    31. })
    32. for (let i = 0; i < files.length; i++) {
    33. let module_name = files[i].replace('.js', '')
    34. let module_path = path.join('../case/page', module_name)
    35. let module_list = {}
    36. module_list[module_name] = require(module_path)
    37. it(`${module_list[module_name].pageName}`, async () => {
    38. if (module_list[module_name].url !== '') {
    39. await page.goto(module_list[module_name].url);
    40. }
    41. await assertElement(page, module_list[module_name].assertElement)
    42. if (module_list[module_name].steps.length !== 0) {
    43. for (let j of module_list[module_name].steps) {
    44. if (j.hasOwnProperty('type')) {
    45. if (j.type === 'input') {
    46. await page.fill(j.selector, j.value)
    47. } else {
    48. await page.click(j.selector)
    49. await page.waitForLoadState()
    50. }
    51. }
    52. }
    53. }
    54. })
    55. }
    56. afterEach(async function () {
    57. })
    58. afterAll(async () => {
    59. page.close()
    60. browser.close()
    61. })
    62. })

     6、src/util/

    add_style.js

    1. /**
    2. * @description 给断言的元素添加样式
    3. * @param page
    4. * @param ele
    5. * @param num 传0代表通过标记为绿色,1代表value没匹配,标记为红色
    6. * @return {Promise}
    7. */
    8. async function addStyle(page, ele, num) {
    9. const elementHandle = await page.$(ele)
    10. if (num === 0) {
    11. await elementHandle.evaluateHandle((ele) => ele.style = "border-style:solid;border-width:2px;border-color:green")
    12. } else if (num === 1) {
    13. await elementHandle.evaluateHandle((ele) => ele.style = "border-style:solid;border-width:2px;border-color:red")
    14. }
    15. await elementHandle.dispose();
    16. }
    17. module.exports = addStyle

    assert_element.js

    1. /**
    2. *@description 断言页面上的元素的value,并且根据结果添加样式进行标记
    3. */
    4. const assert = require('assert')
    5. const addStyle = require('../utils/add_style')
    6. async function assertElements(page, elements) {
    7. for (let i of elements) {
    8. if (i.hasOwnProperty('value') && i.hasOwnProperty('selector')) {
    9. let context = await page.$eval(i.selector, e => e.innerText)
    10. if ((context.replace(' ', '')) === i.value) {
    11. await addStyle(page, i.selector, 0)
    12. assert.ok(`${i.value} matched`)
    13. } else {
    14. await addStyle(page, i.selector, 1)
    15. assert.fail(`${i.value} not matched`)
    16. }
    17. }
    18. }
    19. }
    20. module.exports = assertElements

    file_load.js

    1. /**
    2. * @description 动态加载case中的文件,并且依据config/setting中的orderList进行排序
    3. */
    4. const fs = require('fs')
    5. const setting = require('../config/setting')
    6. module.exports = (path) => {
    7. let file = fs.readdirSync(path)
    8. for (let i in setting.orderList) {
    9. for (let j in file) {
    10. if (new RegExp(setting.orderList[i]).test(file[j])) {
    11. let temp = file[j]
    12. file[j] = file[i]
    13. file[i] = temp
    14. }
    15. }
    16. }
    17. return file
    18. }

    file_temp_log.js

    1. /**
    2. * @description 生成日志
    3. */
    4. const fs = require('fs')
    5. const path = require('path')
    6. const os = require('os')
    7. function tempLog(log_path, data) {
    8. let config = path.join(__dirname.replace('utils', ''), log_path)
    9. if (!fs.existsSync(config)) {
    10. fs.writeFileSync(config, data, 'utf8')
    11. } else {
    12. fs.appendFileSync(config, `${os.EOL}${data}`, 'utf8')
    13. }
    14. }
    15. module.exports = tempLog

    最终效果

  • 相关阅读:
    【计算机网络】P1 计算机网络概念、组成、功能、分类、标准化工作以及性能评估指标
    【动画进阶】巧用 CSS/SVG 实现复杂线条光效动画
    代理IP与Socks5代理:跨界电商时代的网络安全与数据引擎
    基于51单片机的孵化环境温湿度监控系统设计
    C++ 多态语法点
    微信JSAPI支付对接
    el-table的一些样式总结
    通师高专科技创新社训练赛(20221127)
    springcloud05——Zookeeper实现支付微服务
    [PAT-Advanced] B1020/A1070. Mooncake (25)
  • 原文地址:https://blog.csdn.net/LetsStudy/article/details/126484238