在上期教程中咱們介紹了讀寫Excel與使用Selenium的入門方法,本期將介紹經過Vue+Spring Boot實如今WebApp中上傳Excel導入測試腳本的功能。使用先後端分離的技術是由於這樣便於後續功能的迭代,在本文中咱們只涉及一個簡單的前端界面及一個簡單的後臺服務。javascript
運行結果展現與源碼地址在文末,上期傳送門:Java自動化——使用Selenium+POI實現Excel自動化批量查單詞css
使用Vue-Cli建立前端項目html
運用vue-cli工具能夠很輕鬆地構建前端項目,固然,使用WebStorm來構建會更加簡潔(如圖)。本文推薦使用WebStorm,由於在後續開發中,IDE會使咱們的開發更加簡潔。部分配置如圖:前端
Navbar編寫vue
做爲一個WebApp,Navbar做爲應用的導航欄是必不可少的。在本項目中,筆者引入了bootstrap對Navbar進行了輕鬆地構建。在vue中咱們須要在components文件夾中將咱們的組件加進去,對於本工程來講,Navbar是咱們要加入的第一個組件,他獨立於router以外,一直固定在網頁上方。java
2.1 首先,咱們使用npm來安裝vue,vue-cli,bootstrapjquery
npm install vue
npm install -g vue-cli
npm install --save bootstrap jquery popper.js
複製代碼
2.2 接下來咱們在components目錄下new一個vue組件,而且在main.js中引入bootstrap依賴:ios
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.min'
複製代碼
2.3 下面就能夠開始寫代碼了,因爲本文只關注table相關的功能,因此導航欄中除了Script意外的元素都已經disable,代碼以下:git
<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<span class="navbar-brand mb-0 h1">Vue-SpringBoot</span>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<router-link class="nav-link" to="/home">Home</router-link>
</li>
<li class="nav-item active">
<router-link to="/" class="nav-link">Script</router-link>
</li>
<li class="nav-item">
<router-link to="/history" class="nav-link">History</router-link>
</li>
</ul>
</div>
</nav>
</template>
<script> export default { name: "MyNavbar" } </script>
<style scoped> </style>
複製代碼
2.3 在App.vue中引入MyNavbarweb
Script Table編寫
做爲自動化工具,必不可少的一部分就是引入Script,咱們但願用戶可以自由地使用H5界面進行Script的編寫,所以在這裏使用了vue的數據雙向綁定進行Table CRUD。
3.1 新建一個vue組件ScriptTable,代碼以下:
<template>
<div class="container-fluid" id="scriptTable">
<h3>My Script</h3>
<form style="margin-top: 1rem">
<input type="file" @change="getFile($event)" class="" multiple/>
<input type="button" value="upload" @click="submit($event)" class="btn btn-dark">
</form>
<table class="table table-hover text-center table-bordered" style="word-break: break-all; word-wrap: break-word;margin-top: 1rem;">
<thead>
<th>#</th>
<th>Platform</th>
<th>Action</th>
<th>Path</th>
<th>Value</th>
<th>Wait</th>
<th>Screenshot</th>
<th>Change</th>
</thead>
<tbody>
<tr v-cloak v-for="(item, index) in steps">
<th>{{index+1}}</th>
<td>{{item.platform}}</td>
<td>{{item.action}}</td>
<td>{{item.path}}</td>
<td>{{item.value}}</td>
<td>{{item.wait}}</td>
<td>{{item.screenshot}}</td>
<td><a href="#" v-on:click="edit(item)">Edit</a> | <a href="#" v-on:click='aaa(index)'>Delete</a>
</td>
</tr>
<tr>
<th></th>
<td><select class="form-control" v-model="stepstemp.platform">
<option>Web</option>
<option>Android</option>
</select></td>
<td><select class="form-control" v-model="stepstemp.action">
<option>click</option>
<option>get</option>
<option>input</option>
<option>swipe</option>
</select></td>
<td><input class="form-control" v-model="stepstemp.path" placeholder="Enter the xPath"></td>
<td><input class="form-control" v-model="stepstemp.value" placeholder="Enter the input value"></td>
<td><input class="form-control" v-model="stepstemp.wait" placeholder="Waiting seconds"></td>
<td><select class="form-control" v-model="stepstemp.screenshot">
<option>yes</option>
<option>no</option>
</select></td>
<td>
<button class="btn btn-sm btn-dark" v-on:click='save' v-if="isNotEdit">Save</button>
<button class="btn btn-sm btn-primary" v-on:click='saveEdit' v-else>SaveEdit</button>
</td>
</tr>
</tbody>
</table>
<hr/>
</div>
</template>
<script> import Vue from 'vue' import axios from 'axios' export default { name: "ScriptTable", data() { return ({ steps: [], stepstemp: { platform: '', action: '', path: '', value: '', wait: '', screenshot: '' }, isNotEdit: true }); }, methods: { save: function () { this.steps.push(this.stepstemp); this.stepstemp = { platform: '', action: '', path: '', value: '', wait: '', screenshot: '' }; }, aaa: function (index) { this.steps.splice(index, 1) }, edit: function (item) { this.isNotEdit = false; this.stepstemp = item; }, saveEdit: function () { this.isNotEdit = true; this.stepstemp = { platform: '', action: '', path: '', value: '', wait: '', screenshot: '' }; } } } </script>
<style scoped> </style>
複製代碼
3.3 運行dev,打開localhost:8080
npm run dev
複製代碼
前端頁面效果以下:
使用Spring Initializr建立後端項目
爲了更輕鬆地構建工程,構建RESTful API以及更輕鬆地配置請求處理,筆者選擇了Spring Boot做爲後端框架。
4.1 首先咱們使用IDEA集成的Spring Initializr來構建項目,部分配置如圖:
4.2 接下來在pom.xml中引入poi依賴,點擊import change。以下所示:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.0</version>
</dependency>
複製代碼
4.3 接下來咱們在application.properties中配置server.port=8088,與前端項目分開
pojo類Step的編寫
下面是對pojo類的編寫,本文所需的pojo只有Step一種,與前端的table相對應,代碼以下:
import lombok.Data;
@Data
public class Step {
private String platform;
private String action;
private String path;
private String value;
private int wait;
private String screenshot;
}
複製代碼
UploadController的編寫
接下來是對前端Post請求的Handler(Controller)進行編寫,咱們將上傳這個Post請求與"/uploadfile"相對應,注意加入@CrossOrigin註解實現跨域,代碼以下:
package com.daniel.vuespringbootuploadbe;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Controller
@CrossOrigin
@ResponseBody
public class UploadController {
private static String UPLOADED_FOLDER = "src/main/resources/static/temp/";
@Autowired
private LoadService loadService;
@PostMapping("/upload")
public List<Step> singleFileUpload(MultipartFile file) {
try {
// Get the file and save it somewhere
byte[] bytes = file.getBytes();
Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
Files.write(path, bytes);
} catch (IOException e) {
e.printStackTrace();
}
// Print file data to html
List<Step> result = loadService.castToStep(new File(UPLOADED_FOLDER + file.getOriginalFilename()));
return result;
}
}
複製代碼
LoadService的編寫
下面該編寫Service來讀取請求中傳送的文件了,簡單地來講只有一個步驟,將Excel中的Script轉換爲pojo的鏈表並在Controller中做爲ResponseBody返回.
7.1 首先建立Service接口,代碼以下:
package com.daniel.vuespringbootuploadbe;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.List;
@Service
public interface LoadService {
List<Step> castToStep(File file);
}
複製代碼
7.2 接下來建立Service實現類,代碼以下:
package com.daniel.vuespringbootuploadbe;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Service
public class LoadServiceImpl implements LoadService {
@Override
public List<Step> castToStep(File file) {
List<Step> steps = new ArrayList<>();
Workbook workbook = null;
try {
workbook = new XSSFWorkbook(file);
} catch (IOException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
}
Sheet sheet = workbook.getSheetAt(0);
int num = sheet.getLastRowNum() - sheet.getFirstRowNum();
//Read steps
for (int i = 0; i < num; i++) {
Row row = sheet.getRow(i+1);
Step step = new Step();
step.setPlatform(row.getCell(0).getStringCellValue());
step.setAction(row.getCell(1).getStringCellValue());
step.setPath(row.getCell(2).getStringCellValue());
step.setValue(row.getCell(3).getStringCellValue());
step.setWait((int) row.getCell(4).getNumericCellValue());
step.setScreenshot(row.getCell(5).getStringCellValue());
steps.add(step);
}
try {
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
return steps;
}
}
複製代碼
搭建簡單的RESTful API
文章臨近尾聲,如今先後端的獨立代碼基本開發完畢,是時候搭建RESTful了,本文中的API很是簡單,就是對上傳作出響應,並將返回的json寫入界面上的Table中,完成Script導入,npm安裝axios後,在ScriptTable組件中加入以下代碼:
getFile: function (event) {
this.file = event.target.files[0];
console.log(this.file);
},
submit: function (event) {
event.preventDefault();
let formData = new FormData();
formData.append("file", this.file);
axios.post('http://localhost:8088/upload', formData)
.then(function (response) {
for (let i = 0; i < response.data.length; i++) {
var tempData = {
platform: response.data[i].platform,
action: response.data[i].action,
path: response.data[i].path,
value: response.data[i].value,
wait: response.data[i].wait,
screenshot: response.data[i].screenshot
};
this.steps.push(tempData);
}
}.bind(this))
.catch(function (error) {
alert("Fail");
console.log(error);
});
}
複製代碼
運行服務,編寫Script並上傳
接下來咱們建立一個Excel,按如圖格式編寫簡單Script,運行先後端服務,實現上傳:
運行後,Excel文件會上傳到後端工程的static的temp目錄中
本文只是實現了基礎的上傳腳本功能,要實現腳本運行,咱們還要在BE項目中實現相關服務進行封裝,須要Selenium的幫助。以後的教程中會作詳細闡述,敬請期待。
源碼地址:
前端項目——gitee.com/daniel_ddd/…
後端項目——gitee.com/daniel_ddd/…