LeetCode 算法题 BAT 字节跳动 拼多多 美团 京东大厂面试题集锦

在 iRecorder (Web IDE , Web UI 自

2018-12-12  本文已影响26人  光剑书架上的书

在 iRecorder (Web IDE , Web UI 自动化测试平台)中添加上传云端平台的功能

后端代码(Kotlin):

@Controller
class UiTestCaseController {
    @Autowired
    lateinit var uiTestCaseMapper: UiTestCaseMapper
    @Autowired
    lateinit var employeeMapper: EmployeeMapper

    class IRecorderSideJsonData {
        lateinit var name: String
        lateinit var sideJson: String
        lateinit var token: String
    }

    @RequestMapping(value = ["/uitestcase/upload.api"], method = [RequestMethod.POST])
    @ResponseBody
    fun upload(@RequestBody irecorderSideJsonData: IRecorderSideJsonData): Boolean {

        val name = irecorderSideJsonData.name
        val sideJson = irecorderSideJsonData.sideJson
        val token = irecorderSideJsonData.token

        println("name=${name}")
        println("sideJson=${sideJson}")
        println("token=${token}")

        val uiTestCase: UiTestCase? = uiTestCaseMapper.findByToken(token)
        try {
            if (uiTestCase == null) {// 新记录
                val newRecord = UiTestCase()
                newRecord.gmtCreate = Date()
                newRecord.gmtModified = Date()
                newRecord.isDeleted = 0
                newRecord.name = name
                newRecord.sideJson = sideJson
                newRecord.token = token
                uiTestCaseMapper.insert(newRecord)
                return true
            } else { // 更新记录
                //  update 核心字段
                uiTestCase.gmtModified = Date()
                uiTestCase.name = name
                uiTestCase.sideJson = sideJson
                uiTestCaseMapper.updateByPrimaryKeyWithBLOBs(uiTestCase)
                return true
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    @RequestMapping(value = ["/uitestcase/get.api"], method = [RequestMethod.GET])
    @ResponseBody
    fun get(token: String): UiTestCase {
        return uiTestCaseMapper.findByToken(token)
    }

    //    http://127.0.0.1:9000/getTestCase?token=aac0a0bc-9f06-402c-b44e-ecbfa29f1b90
    @RequestMapping(value = ["/getTestCase"], method = [RequestMethod.GET])
    fun getTestCase(token: String, model: Model, request: HttpServletRequest, response: HttpServletResponse): String {
        val loginUser = SimpleUserUtil.getBucSSOUser(request);
        val empId = loginUser.empId

        println("loginUser:${JSON.toJSONString(loginUser)}")
        val uiTestCase = uiTestCaseMapper.findByToken(token)
        println("uiTestCase===${JSON.toJSONString(uiTestCase)}")
        if (uiTestCase.owner == null) { // 首次带着该 token 打开链接,回写当前打开人的工号到 owner 字段
            uiTestCase.owner = empId
            uiTestCaseMapper.updateByPrimaryKeyWithBLOBs(uiTestCase)
        }

        model.addAttribute("uiTestCase", uiTestCase)

        val emp: Employee? = employeeMapper.findByWorkno(uiTestCase.owner)
        var ownerText = emp?.empName
        model.addAttribute("ownerText", ownerText)

        val sideJson = uiTestCase.sideJson
        println("sideJson=$sideJson")
        val sideJsonDto = JSON.parseObject<SideJsonDto>(sideJson, SideJsonDto::class.java)
        println("sideJsonDto = " + JSON.toJSONString(sideJsonDto))
        model.addAttribute("sideJsonDto", sideJsonDto)
        return "/testcase"
    }

    @RequestMapping(value = ["", "/", "/listTestCase"], method = [RequestMethod.GET])
    fun listTestCase(model: Model, request: HttpServletRequest, response: HttpServletResponse): String {
        val loginUser = SimpleUserUtil.getBucSSOUser(request);
        val empId = loginUser.empId
        println("loginUser:${JSON.toJSONString(loginUser)}")
        val list = uiTestCaseMapper.listTestCase(empId)
        println(JSON.toJSONString(list))

        model.addAttribute("list", list)
        return "/testcaselist"
    }

}

前端代码(React jsx):

import browser from 'webextension-polyfill'
import {js_beautify as beautify} from 'js-beautify'
import UpgradeProject from './migrate'
import {FileTypes, migrateProject, migrateTestCase, migrateUrls, verifyFile,} from './legacy/migrate'
import TestCase from '../models/TestCase'
import UiState from '../stores/view/UiState'
import PlaybackState from '../stores/view/PlaybackState'
import ModalState from '../stores/view/ModalState'
import Selianize, {ParseError} from 'selianize'
import Manager from '../../plugin/manager'
import chromeGetFile from './filesystem/chrome'
import firefoxGetFile from './filesystem/firefox'
import {userAgent as parsedUA} from '../../common/utils'
import uuidv4 from 'uuid/v4'

export const supportedFileFormats = '.side, text/html'

export function getFile(path) {
  const browserName = parsedUA.browser.name
  return (() => {
    if (browserName === 'Chrome') {
      return chromeGetFile(path)
    } else if (browserName === 'Firefox') {
      return firefoxGetFile(path)
    } else {
      return Promise.reject(
        new Error('Operation is not supported in this browser')
      )
    }
  })().then(blob => {
    return new Promise(res => {
      const reader = new FileReader()
      reader.addEventListener('load', () => {
        res(reader.result)
      })
      reader.readAsDataURL(blob)
    })
  })
}

export function loadAsText(blob) {
  return new Promise(res => {
    const fileReader = new FileReader()
    fileReader.onload = e => {
      res(e.target.result)
    }

    fileReader.readAsText(blob)
  })
}

export function saveProject(_project) {
  const project = _project.toJS()
  downloadProject(project)
  UiState.saved()
}

/**
 * 原生 js 的 Ajax 函数
 * @type {{get: Ajax.get, post: Ajax.post}}
 */
const Ajax = {
  get: function (url, fn) {
    // XMLHttpRequest对象用于在后台与服务器交换数据
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url, true)
    xhr.onreadystatechange = function () {
      // readyState == 4说明请求已完成
      if ((xhr.readyState == 4 && xhr.status == 200) || xhr.status == 304) {
        // 从服务器获得数据
        fn.call(this, xhr.responseText)
      }
    }
    xhr.send()
  },
  // data应为'a=a1&b=b1'这种字符串格式,在jq里如果data为对象会自动将对象转成这种字符串格式
  post: function (url, data, fn) {
    var xhr = new XMLHttpRequest()
    xhr.open('POST', url, true)
    // 添加http头,发送信息至服务器时内容编码类型
    xhr.setRequestHeader('Content-Type', 'application/json')
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
        fn.call(this, xhr.responseText)
      }
    }
    xhr.send(data)
  },
}

export function uploadProject(_project) {
  const project = _project.toJS()
  const sideJson = JSON.stringify(project)
  const token = uuidv4()
  const host = 'http://localhost:9000'

  let data = {
    name: project.name,
    sideJson: sideJson,
    token: token
  }

  console.log(`data===>${JSON.stringify(data)}`)

  Ajax.post(`${host}/uitestcase/upload.api`, JSON.stringify(data), res => {
    console.log(`${host}/uitestcase/upload.api result: ${res}`)
    ModalState.showAlert({
      title: '上传云端',
      description: `上传成功,前往小U平台查看用例:  ${host}/getTestCase?token=${token}`,
      confirmLabel: '确定',
      cancelLabel: '取消',
    }).then(choseConfirm => {
      console.log(`choseConfirm=${choseConfirm}`)
      if (choseConfirm) {
        window.open(`${host}/getTestCase?token=${token}`, '_blank')
      }
    })
  })
}

function downloadProject(project) {
  return exportProject(project).then(snapshot => {
    if (snapshot) {
      project.snapshot = snapshot
      Object.assign(project, Manager.emitDependencies())
    }
    return browser.downloads.download({
      filename: project.name + '.side',
      url: createBlob(
        'application/json',
        beautify(JSON.stringify(project), {indent_size: 2})
      ),
      saveAs: true,
      conflictAction: 'overwrite',
    })
  })
}

function exportProject(project) {
  return Manager.validatePluginExport(project).then(() => {
    return Selianize(project, {
      silenceErrors: true,
      skipStdLibEmitting: true,
    }).catch(err => {
      const markdown = ParseError((err && err.message) || err)
      ModalState.showAlert({
        title: 'Error saving project',
        description: markdown,
        confirmLabel: 'Download log',
        cancelLabel: 'Close',
      }).then(choseDownload => {
        if (choseDownload) {
          browser.downloads.download({
            filename: project.name + '-logs.md',
            url: createBlob('text/markdown', markdown),
            saveAs: true,
            conflictAction: 'overwrite',
          })
        }
      })
      return Promise.reject()
    })
  })
}

let previousFile = null

// eslint-disable-next-line
function createBlob(mimeType, data) {
  const blob = new Blob([data], {
    type: 'text/plain',
  })
  // If we are replacing a previously generated file we need to
  // manually revoke the object URL to avoid memory leaks.
  if (previousFile !== null) {
    window.URL.revokeObjectURL(previousFile)
  }
  previousFile = window.URL.createObjectURL(blob)
  return previousFile
}

export function loadProject(project, file) {
  function displayError(error) {
    ModalState.showAlert({
      title: 'Error migrating project',
      description: error.message,
      confirmLabel: 'Close',
    })
  }

  loadAsText(file).then(contents => {
    if (/\.side$/.test(file.name)) {
      loadJSProject(project, UpgradeProject(JSON.parse(contents)))
    } else {
      try {
        const type = verifyFile(contents)
        if (type === FileTypes.Suite) {
          ModalState.importSuite(contents, files => {
            try {
              loadJSProject(project, migrateProject(files))
            } catch (error) {
              displayError(error)
            }
          })
        } else if (type === FileTypes.TestCase) {
          let {test, baseUrl} = migrateTestCase(contents)
          if (project.urls.length && !project.urls.includes(baseUrl)) {
            ModalState.showAlert({
              title: 'Migrate test case',
              description: `The test case you're trying to migrate has a different base URL (${baseUrl}) than the project's one.  \nIn order to migrate the test case URLs will be made absolute.`,
              confirmLabel: 'Migrate',
              cancelLabel: 'Discard',
            }).then(choseMigration => {
              if (choseMigration) {
                UiState.selectTest(
                  project.addTestCase(
                    TestCase.fromJS(migrateUrls(test, baseUrl))
                  )
                )
              }
            })
          } else {
            UiState.selectTest(
              project.addTestCase(TestCase.fromJS(test, baseUrl))
            )
          }
        }
      } catch (error) {
        displayError(error)
      }
    }
  })
}

export function loadJSProject(project, data) {
  UiState.changeView('Tests')
  PlaybackState.clearPlayingCache()
  UiState.clearViewCache()
  project.fromJS(data)
  UiState.projectChanged()
  Manager.emitMessage({
    action: 'event',
    event: 'projectLoaded',
    options: {
      projectName: project.name,
      projectId: project.id,
    },
  })
}


side runner 代码

#!/usr/bin/env node

// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

import fs from 'fs'
import path from 'path'
import crypto from 'crypto'
import util from 'util'
import { fork } from 'child_process'
import program from 'commander'
import winston from 'winston'
import glob from 'glob'
import rimraf from 'rimraf'
import { js_beautify as beautify } from 'js-beautify'
import Selianize, { getUtilsFile } from 'selianize'
import Capabilities from './capabilities'
import ParseProxy from './proxy'
import Config from './config'
import Satisfies from './versioner'
import metadata from '../package.json'

const DEFAULT_TIMEOUT = 15000

process.title = metadata.name

program
  .usage('[options] project.side [project.side] [*.side]')
  .version(metadata.version)
  .option('-c, --capabilities [list]', 'Webdriver capabilities')
  .option('-s, --server [url]', 'Webdriver remote server')
  .option('-p, --params [list]', 'General parameters')
  .option('-f, --filter [string]', 'Run suites matching name')
  .option(
    '-w, --max-workers [number]',
    'Maximum amount of workers that will run your tests, defaults to number of cores'
  )
  .option('--base-url [url]', 'Override the base URL that was set in the IDE')
  .option(
    '--timeout [number | undefined]',
    `The maximimum amount of time, in milliseconds, to spend attempting to locate an element. (default: ${DEFAULT_TIMEOUT})`
  )
  .option(
    '--proxy-type [type]',
    'Type of proxy to use (one of: direct, manual, pac, socks, system)'
  )
  .option(
    '--proxy-options [list]',
    'Proxy options to pass, for use with manual, pac and socks proxies'
  )
  .option(
    '--configuration-file [filepath]',
    'Use specified YAML file for configuration. (default: .side.yml)'
  )
  .option(
    '--output-directory [directory]',
    'Write test results to files, results written in JSON'
  )
  .option(
    '--force',
    "Forcibly run the project, regardless of project's version"
  )
  .option('--debug', 'Print debug logs')

if (process.env.NODE_ENV === 'development') {
  program.option(
    '-e, --extract',
    'Only extract the project file to code (this feature is for debugging purposes)'
  )
  program.option(
    '-r, --run [directory]',
    'Run the extracted project files (this feature is for debugging purposes)'
  )
}

program.parse(process.argv)

if (!program.args.length && !program.run) {
  program.outputHelp()
  process.exit(1)
}

winston.cli()
winston.level = program.debug ? 'debug' : 'info'

if (program.extract || program.run) {
  winston.warn(
    "This feature is used by iRecorder IDE maintainers for debugging purposes, we hope you know what you're doing!"
  )
}

const configuration = {
  capabilities: {
    browserName: 'chrome',
  },
  params: {},
  runId: crypto.randomBytes(16).toString('hex'),
  path: path.join(__dirname, '../../'),
}

const confPath = program.configurationFile || '.side.yml'
const configurationFilePath = path.isAbsolute(confPath)
  ? confPath
  : path.join(process.cwd(), confPath)
try {
  Object.assign(configuration, Config.load(configurationFilePath))
} catch (e) {
  winston.debug('Could not load ' + configurationFilePath)
}

program.filter = program.filter || '*'
configuration.server = program.server ? program.server : configuration.server

configuration.timeout = program.timeout
  ? +program.timeout
  : configuration.timeout
    ? +configuration.timeout
    : DEFAULT_TIMEOUT // eslint-disable-line indent

if (configuration.timeout === 'undefined') configuration.timeout = undefined

if (program.capabilities) {
  try {
    configuration.capabilities = Capabilities.parseString(program.capabilities)
  } catch (e) {
    winston.debug('Failed to parse inline capabilities')
  }
}

if (program.params) {
  try {
    configuration.params = Capabilities.parseString(program.params)
  } catch (e) {
    winston.debug('Failed to parse additional params')
  }
}

if (program.proxyType) {
  try {
    let opts = program.proxyOptions
    if (program.proxyType === 'manual' || program.proxyType === 'socks') {
      opts = Capabilities.parseString(opts)
    }
    const proxy = ParseProxy(program.proxyType, opts)
    Object.assign(configuration, proxy)
  } catch (e) {
    winston.error(e.message)
    process.exit(1)
  }
} else if (configuration.proxyType) {
  try {
    const proxy = ParseProxy(
      configuration.proxyType,
      configuration.proxyOptions
    )
    Object.assign(configuration, proxy)
  } catch (e) {
    winston.error(e.message)
    process.exit(1)
  }
}

configuration.baseUrl = program.baseUrl
  ? program.baseUrl
  : configuration.baseUrl

winston.debug(util.inspect(configuration))

let projectPath

function runProject(project) {
  winston.info(`Running ${project.path}`)
  if (!program.force) {
    let warning
    try {
      warning = Satisfies(project.version, '2.0')
    } catch (e) {
      return Promise.reject(e)
    }
    if (warning) {
      winston.warn(warning)
    }
  } else {
    winston.warn("--force is set, ignoring project's version")
  }
  if (!project.suites.length) {
    return Promise.reject(
      new Error(
        `The project ${
          project.name
        } has no test suites defined, create a suite using the IDE.`
      )
    )
  }
  projectPath = `side-suite-${project.id}`
  rimraf.sync(projectPath)
  fs.mkdirSync(projectPath)
  fs.writeFileSync(
    path.join(projectPath, 'package.json'),
    JSON.stringify(
      {
        name: project.name,
        version: '0.0.0',
        jest: {
          modulePaths: [path.join(__dirname, '../node_modules')],
          setupTestFrameworkScriptFile: require.resolve(
            'jest-environment-selenium/dist/setup.js'
          ),
          testEnvironment: 'jest-environment-selenium',
          testEnvironmentOptions: configuration,
        },
        dependencies: project.dependencies || {},
      },
      null,
      2
    )
  )

  return Selianize(project, { silenceErrors: true }, project.snapshot).then(
    code => {
      const tests = code.tests
        .reduce((tests, test) => {
          return (tests += test.code)
        }, 'const utils = require("./utils.js");const tests = {};')
        .concat('module.exports = tests;')
      writeJSFile(path.join(projectPath, 'commons'), tests, '.js')
      writeJSFile(path.join(projectPath, 'utils'), getUtilsFile(), '.js')
      code.suites.forEach(suite => {
        if (!suite.tests) {
          // not parallel
          const cleanup = suite.persistSession
            ? ''
            : 'beforeEach(() => {vars = {};});afterEach(async () => (cleanup()));'
          writeJSFile(
            path.join(projectPath, suite.name),
            `// This file was generated using iRecorder (Jason Chimpanzee).\nconst tests = require("./commons.js");${
              code.globalConfig
            }${suite.code}${cleanup}`
          )
        } else if (suite.tests.length) {
          fs.mkdirSync(path.join(projectPath, suite.name))
          // parallel suite
          suite.tests.forEach(test => {
            writeJSFile(
              path.join(projectPath, suite.name, test.name),
              `// This file was generated using iRecorder (Jason Chimpanzee).\nconst tests = require("../commons.js");${
                code.globalConfig
              }${test.code}`
            )
          })
        }
      })

      return new Promise((resolve, reject) => {
        let npmInstall
        if (project.dependencies && Object.keys(project.dependencies).length) {
          npmInstall = new Promise((resolve, reject) => {
            const child = fork(require.resolve('./npm'), {
              cwd: path.join(process.cwd(), projectPath),
              stdio: 'inherit',
            })
            child.on('exit', code => {
              if (code) {
                reject()
              } else {
                resolve()
              }
            })
          })
        } else {
          npmInstall = Promise.resolve()
        }
        npmInstall
          .then(() => {
            if (program.extract) {
              resolve()
            } else {
              runJest(project) // main run
                .then(resolve)
                .catch(reject)
            }
          })
          .catch(reject)
      })
    }
  )
}

/**
 * run the auto generated jest test project
 * @param project
 * @returns {Promise}
 */
function runJest(project) {
  return new Promise((resolve, reject) => {
    const args = [
      '--testMatch',
      `{**/*${program.filter}*/*.test.js,**/*${program.filter}*.test.js}`,
    ]
      .concat(program.maxWorkers ? ['-w', program.maxWorkers] : [])
      .concat(
        program.outputDirectory
          ? [
              '--json',
              '--outputFile',
              path.isAbsolute(program.outputDirectory)
                ? path.join(program.outputDirectory, `${project.name}.json`)
                : '../' +
                  path.join(program.outputDirectory, `${project.name}.json`),
            ]
          : []
      )
    const opts = {
      cwd: path.join(process.cwd(), projectPath),
      stdio: 'inherit',
    }
    winston.debug('jest worker args')
    winston.debug(args)
    winston.debug('jest work opts')
    winston.debug(opts)
    const child = fork(require.resolve('./child'), args, opts)

    child.on('exit', code => {
      console.log('') // eslint-disable-line no-console
      // when is not running
      if (!program.run) {
        // uncomment this: keep the jest project files
        // rimraf.sync(projectPath) // delete the project files
      }
      if (code) {
        reject()
      } else {
        resolve()
      }
    })
  })
}

function runAll(projects, index = 0) {
  if (index >= projects.length) return Promise.resolve()
  return runProject(projects[index])
    .then(() => {
      return runAll(projects, ++index)
    })
    .catch(error => {
      process.exitCode = 1
      error && winston.error(error.message + '\n')
      return runAll(projects, ++index)
    })
}

function writeJSFile(name, data, postfix = '.test.js') {
  fs.writeFileSync(`${name}${postfix}`, beautify(data, { indent_size: 2 }))
}

const projects = [
  ...program.args.reduce((projects, project) => {
    glob.sync(project).forEach(p => {
      projects.add(p)
    })
    return projects
  }, new Set()),
].map(p => {
  const project = JSON.parse(fs.readFileSync(p))
  project.path = p
  return project
})

function handleQuit(_signal, code) {
  if (!program.run) {
    rimraf.sync(projectPath)
  }
  process.exit(code)
}

process.on('SIGINT', handleQuit)
process.on('SIGTERM', handleQuit)

if (program.run) {
  projectPath = program.run
  runJest({
    name: 'test',
  }).catch(winston.error)
} else {
  runAll(projects)
}

上一篇 下一篇

猜你喜欢

热点阅读