Dynamic Debugging!!!

This commit is contained in:
naibo 2023-12-13 05:50:23 +08:00
parent c3bab575f4
commit 0c30b27756
35 changed files with 947 additions and 1398 deletions

View File

@ -0,0 +1 @@
{"id":7,"name":"京东全球版-专业的综合网上购物商城","url":"https://www.jd.com","links":"https://www.jd.com","create_time":"12/11/2023, 7:29:08 AM","update_time":"12/11/2023, 8:02:45 AM","version":"0.6.0","saveThreshold":10,"quitWaitTime":60,"environment":0,"maximizeWindow":0,"maxViewLength":15,"recordLog":1,"outputFormat":"xlsx","saveName":"current_time","inputExcel":"","startFromExit":0,"pauseKey":"p","containJudge":false,"desc":"https://www.jd.com","inputParameters":[{"id":0,"name":"urlList_0","nodeId":1,"nodeName":"打开网页","value":"https://www.jd.com","desc":"要采集的网址列表,多行以\\n分开","type":"text","exampleValue":"https://www.jd.com"},{"id":1,"name":"inputText_1","nodeName":"输入文字","nodeId":4,"desc":"要输入的文本,如京东搜索框输入:电脑","type":"text","exampleValue":"Field[\"参数1\"]","value":"Field[\"参数1\"]"}],"outputParameters":[{"id":0,"name":"参数1","desc":"","type":"text","recordASField":1,"exampleValue":"/手机/数码"}],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[1,2],"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0},"isInLoop":false},{"id":1,"index":1,"parentId":0,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":false,"position":0,"parameters":{"useLoop":false,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"url":"https://www.jd.com","links":"https://www.jd.com","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}},{"id":2,"index":2,"parentId":0,"type":1,"option":8,"title":"循环采集数据","sequence":[3,4],"isInLoop":false,"position":1,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"/html/body/div[5]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"loopType":1,"pathList":"","textList":"","code":"","waitTime":0,"exitCount":0,"exitElement":"//body","historyWait":2,"breakMode":0,"breakCode":"","breakCodeWaitTime":0,"allXPaths":["/html/body/div[5]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]","//div[contains(., '/手机/数码')]","//DIV[@class='LeftSide_menu_item__SBMWC LeftSide_text_space__2UhbG ']","/html/body/div[last()-5]/div/div[last()-4]/div/div[last()-2]/div/div/div/div[last()-1]/div[last()-12]"]}},{"id":3,"index":3,"parentId":2,"type":0,"option":3,"title":"提取数据","sequence":[],"isInLoop":true,"position":0,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"clear":0,"newLine":1,"paras":[{"nodeType":0,"contentType":7,"relative":true,"name":"参数1","desc":"","extractType":0,"relativeXPath":"","allXPaths":"","exampleValues":[{"num":0,"value":"/手机/数码"}],"unique_index":"kfd57x5t8clq048p17","iframe":false,"default":"","paraType":"text","recordASField":1,"beforeJS":"","beforeJSWaitTime":0,"JS":"","JSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"downloadPic":0}],"loopType":1}},{"id":4,"index":4,"parentId":2,"type":0,"option":4,"title":"输入文字","sequence":[],"isInLoop":true,"position":1,"parameters":{"history":3,"tabIndex":-1,"useLoop":false,"xpath":"//*[@id=\"key\"]","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"value":"Field[\"参数1\"]","index":0,"allXPaths":["/html/body/div[4]/div[1]/div[2]/div[1]/input[1]","//input[contains(., '')]","id(\"key\")","//INPUT[@class='text defcolor']","/html/body/div[last()-6]/div/div[last()-2]/div/input"]}}]}

View File

@ -256,7 +256,7 @@ async function findElementAcrossAllWindows(msg, notifyBrowser = true, scrollInto
len = len - 1; len = len - 1;
if (!notify) { if (!notify) {
notify = true; notify = true;
notify_browser("正在尝试在其他窗口中查找元素,请耐心等待。", "Trying to find elements in other windows, please wait patiently.", "info"); // notify_browser("正在尝试在其他窗口中查找元素,请耐心等待。", "Trying to find elements in other windows, please wait patiently.", "info");
} }
if (len == 0) { if (len == 0) {
break; break;
@ -306,7 +306,6 @@ async function beginInvoke(msg, ws) {
let height = parseInt(size.height * 0.6) let height = parseInt(size.height * 0.6)
window.setBounds({x: 0, y: size.height * 0.4, height: height, width: width}); window.setBounds({x: 0, y: size.height * 0.4, height: height, width: width});
} }
flowchart_window.show(); flowchart_window.show();
// flowchart_window.openDevTools(); // flowchart_window.openDevTools();
} else if (msg.type == 2) { } else if (msg.type == 2) {
@ -345,7 +344,108 @@ async function beginInvoke(msg, ws) {
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
} else if (msg.type == 4) { //试运行功能 } else if (msg.type == 4) { //标记元素和试运行功能
let node = JSON.parse(msg.message.node);
let type = msg.message.type;
if (type == 0) { //标记元素
let option = node.option;
let parameters = node.parameters;
//下面是让浏览器自动滚动到元素位置
if (option == 2 || option == 4 || option == 6 || option == 7) {
let xpath = parameters.xpath;
let parent_node = JSON.parse(msg.message.parentNode);
if (parameters.useLoop && option != 4 && option != 6) {
let parentXPath = parent_node.parameters.xpath;
xpath = parentXPath + xpath;
}
let elementInfo = {"iframe": parameters.iframe, "xpath": xpath, "id": -1};
//用于跳转到元素位置
let element = await findElementAcrossAllWindows(elementInfo, notifyBrowser = false);
} else if (option == 3) {
let paras = parameters.paras; //所有的提取数据参数
let para = paras[0];
let xpath = para.relativeXPath;
if (para.relative) {
let parent_node = JSON.parse(msg.message.parentNode);
let parent_xpath = parent_node.parameters.xpath;
xpath = parent_xpath + xpath;
}
let elementInfo = {"iframe": para.iframe, "xpath": xpath, "id": -1};
let element = await findElementAcrossAllWindows(elementInfo, notifyBrowser = false);
} else if (option == 11) {
let paras = parameters.paras; //所有的提取数据参数
let i = parameters.index;
let para = paras[i];
let xpath = para.relativeXPath;
if (para.relative) {
let parent_node = JSON.parse(msg.message.parentNode);
let parent_xpath = parent_node.parameters.xpath;
xpath = parent_xpath + xpath;
}
let elementInfo = {"iframe": para.iframe, "xpath": xpath, "id": -1};
let element = await findElementAcrossAllWindows(elementInfo, notifyBrowser = false);
} else if (option == 8) {
// <select v-model='loopType' class="form-control" @change = "handleLoopTypeChange" >
// < option
// :
// value = 0 > 单个元素(多用于循环点击下一页)</option>
// <option :value = 1 > 不固定元素列表 < /option>
// <option :value = 2 > 固定元素列表 < /option>
// <option :value = 3 > 文本列表(多用于循环在文本框输入文本)</option>
// <option :value = 4 > 网址列表(多用于循环打开网页)</option>
// <option :value = 5 > JavaScript命令返回值需以return
// 开头)</option>
// <option :value = 6 > 系统命令返回值 < /option>
// <option :value = 7 > 执行环境下的Python表达式值eval操作</option>
// </select>
//
let loopType = parameters.loopType;
if (loopType <= 2) {
let xpath = "";
if (loopType <= 1) {
xpath = parameters.xpath;
} else if (loopType == 2) {
xpath = parameters.pathList.split("\n")[0].trim();
}
let elementInfo = {"iframe": parameters.iframe, "xpath": xpath, "id": -1};
let element = await findElementAcrossAllWindows(elementInfo, notifyBrowser = false);
} else if (loopType == 5) { //JavaScript命令返回值
let code = parameters.code;
let waitTime = parameters.waitTime;
let element = await driver.findElement(By.tagName("body"));
let outcome = await execute_js(code, element, waitTime);
if (!outcome || outcome == -1) {
notify_browser("目前页面中,设置的循环“" + node.title + "”的JavaScript条件不成立", "The condition of the loop " + node.title + " is not met, skip this loop.", "warning");
} else {
notify_browser("目前页面中,设置的循环“" + node.title + "”的JavaScript条件成立", "The condition of the loop " + node.title + " is met, continue this loop.", "success");
}
}
} else if (option == 10) { //条件分支
let condition = parameters.class; //条件类型
let result = -1;
let additionalInfo = "";
if (condition == 5 || condition == 7) { //JavaScript命令返回值
let code = parameters.code;
let waitTime = parameters.waitTime;
let element = await driver.findElement(By.tagName("body"));
if (condition == 7) {
let parent_node = JSON.parse(msg.message.parentNode);
let xpath = parent_node.parameters.xpath;
elementInfo = {"iframe": parent_node.parameters.iframe, "xpath": xpath, "id": -1};
element = await findElementAcrossAllWindows(elementInfo);
}
let outcome = await execute_js(code, element, waitTime);
if (!outcome) {
msg.message.result = 0; //条件不成立传入扩展
} else if (outcome == -1) {
msg.message.result = -1; //JS执行出错
} else {
msg.message.result = 1; //条件成立传入扩展
}
}
}
send_message_to_browser(JSON.stringify({"type": "trial", "message": msg}));
} else { //试运行
try { try {
let flowchart_url = flowchart_window.webContents.getURL(); let flowchart_url = flowchart_window.webContents.getURL();
} catch { } catch {
@ -354,7 +454,6 @@ async function beginInvoke(msg, ws) {
if (flowchart_window == null) { if (flowchart_window == null) {
notify_flowchart("试运行功能只能在设计任务阶段Chrome浏览器打开时使用", "The trial run function can only be used when designing tasks and opening in Chrome browser!", "error"); notify_flowchart("试运行功能只能在设计任务阶段Chrome浏览器打开时使用", "The trial run function can only be used when designing tasks and opening in Chrome browser!", "error");
} else { } else {
let node = JSON.parse(msg.message.node);
notify_browser("正在试运行操作:" + node.title, "Trying to run the operation: " + node.title, "info"); notify_browser("正在试运行操作:" + node.title, "Trying to run the operation: " + node.title, "info");
let option = node.option; let option = node.option;
let parameters = node.parameters; let parameters = node.parameters;
@ -376,7 +475,12 @@ async function beginInvoke(msg, ws) {
let parent_node = JSON.parse(msg.message.parentNode); let parent_node = JSON.parse(msg.message.parentNode);
url = parent_node["parameters"]["textList"].split("\n")[0]; url = parent_node["parameters"]["textList"].split("\n")[0];
} }
try {
await driver.get(url); await driver.get(url);
} catch (e) {
driver.switchTo().window(current_handle);
await driver.get(url);
}
} else if (option == 2 || option == 7) { //点击事件 } else if (option == 2 || option == 7) { //点击事件
let elementInfo = {"iframe": parameters.iframe, "xpath": parameters.xpath, "id": -1}; let elementInfo = {"iframe": parameters.iframe, "xpath": parameters.xpath, "id": -1};
if (parameters.useLoop) { if (parameters.useLoop) {
@ -384,10 +488,24 @@ async function beginInvoke(msg, ws) {
let parent_xpath = parent_node.parameters.xpath; let parent_xpath = parent_node.parameters.xpath;
elementInfo.xpath = parent_xpath + elementInfo.xpath; elementInfo.xpath = parent_xpath + elementInfo.xpath;
} }
let element = await findElementAcrossAllWindows(elementInfo); let element = await findElementAcrossAllWindows(elementInfo); //通过此函数找到元素并切换到对应的窗口
await execute_js(parameters.beforeJS, element, parameters.beforeJSWaitTime); await execute_js(parameters.beforeJS, element, parameters.beforeJSWaitTime);
if (option == 2) { if (option == 2) {
await click_element(element); await click_element(element);
let alertHandleType = parameters.alertHandleType;
if (alertHandleType == 1) {
try {
await driver.switchTo().alert().accept();
} catch (e) {
console.log("No alert");
}
} else if (alertHandleType == 2) {
try {
await driver.switchTo().alert().dismiss();
} catch (e) {
console.log("No alert");
}
}
} else if (option == 7) { } else if (option == 7) {
await driver.actions().move({origin: element}).perform(); await driver.actions().move({origin: element}).perform();
} }
@ -448,9 +566,15 @@ async function beginInvoke(msg, ws) {
await execute_js(afterJS, element, afterJSWaitTime); await execute_js(afterJS, element, afterJSWaitTime);
} else if (option == 5) { //自定义操作的JS代码 } else if (option == 5) { //自定义操作的JS代码
let code = parameters.code; let code = parameters.code;
let codeMode = parameters.codeMode;
let waitTime = parameters.waitTime; let waitTime = parameters.waitTime;
let element = await driver.findElement(By.tagName("body")); let element = await driver.findElement(By.tagName("body"));
if (codeMode == 0) {
await execute_js(code, element, waitTime); await execute_js(code, element, waitTime);
} else if (codeMode == 8) {
//刷新页面
await driver.navigate().refresh();
}
} else if (option == 6) { //切换下拉选项 } else if (option == 6) { //切换下拉选项
let optionMode = parseInt(parameters.optionMode); let optionMode = parseInt(parameters.optionMode);
let optionValue = parameters.optionValue; let optionValue = parameters.optionValue;
@ -496,10 +620,26 @@ async function beginInvoke(msg, ws) {
throw new Error('Invalid option mode'); throw new Error('Invalid option mode');
} }
execute_js(afterJS, element, afterJSWaitTime); execute_js(afterJS, element, afterJSWaitTime);
} else if (option == 11) { //单个提取数据参数
notify_browser("提示提取数据操作只能试运行设置的JavaScript语句且只针对第一个匹配的元素。", "Hint: can only test JavaScript statement set in the data extraction operation, and only for the first matching element.", "info");
let paras = parameters.paras; //所有的提取数据参数
let i = parameters.index;
let para = paras[i];
let xpath = para.relativeXPath;
if (para.relative) {
let parent_node = JSON.parse(msg.message.parentNode);
let parent_xpath = parent_node.parameters.xpath;
xpath = parent_xpath + xpath;
}
let elementInfo = {"iframe": para.iframe, "xpath": xpath, "id": -1};
let element = await findElementAcrossAllWindows(elementInfo);
if (element != null) {
await execute_js(para.beforeJS, element, para.beforeJSWaitTime);
await execute_js(para.afterJS, element, para.afterJSWaitTime);
}
}
} }
} }
} else if (msg.type == 8) { //展示元素功能
} else if (msg.type == 5) { } else if (msg.type == 5) {
let child = require('child_process').execFile; let child = require('child_process').execFile;
@ -562,6 +702,8 @@ async function beginInvoke(msg, ws) {
async function click_element(element) { async function click_element(element) {
try { try {
await element.click(); await element.click();
//ctrl+click
// await driver.actions().keyDown(Key.CONTROL).click(element).keyUp(Key.CONTROL).perform();
} catch (e) { } catch (e) {
console.log(e); console.log(e);
await driver.executeScript("arguments[0].click();", element); await driver.executeScript("arguments[0].click();", element);
@ -569,17 +711,26 @@ async function click_element(element) {
} }
async function execute_js(js, element, wait_time = 3) { async function execute_js(js, element, wait_time = 3) {
let outcome = 0;
if (js.length != 0) { if (js.length != 0) {
try { try {
await driver.executeScript(js, element); outcome = await driver.executeScript(js, element);
if (wait_time == 0) { if (wait_time == 0) {
wait_time = 30000; wait_time = 30000;
} }
await new Promise(resolve => setTimeout(resolve, wait_time)); // await new Promise(resolve => setTimeout(resolve, wait_time));
} catch (e) { } catch (e) {
// await new Promise(resolve => setTimeout(resolve, 2000));
notify_browser("执行JavaScript出错请检查JavaScript语句是否正确" + js + "\n错误信息" + e, "Error executing JavaScript, please check if the JavaScript statement is correct: " + js + "\nError message: " + e, "error"); notify_browser("执行JavaScript出错请检查JavaScript语句是否正确" + js + "\n错误信息" + e, "Error executing JavaScript, please check if the JavaScript statement is correct: " + js + "\nError message: " + e, "error");
outcome = -1;
}
if (js.indexOf("Field(") >= 0 || js.indexOf("eval(") >= 0) {
//两秒后通知浏览器
await new Promise(resolve => setTimeout(resolve, 2000));
notify_browser("检测到JavaScript中包含Field(\"\")或eval(\"\"),试运行时无法执行两项表达式,请在任务正式调用阶段测试是否有效。", "Field(\"\") or eval(\"\") is detected in JavaScript, and the two expressions cannot be executed during trial operation. Please test whether it is valid in the formal call stage.", "warning");
} }
} }
return outcome;
} }
function notify_flowchart(msg_zh, msg_en, level = "info") { function notify_flowchart(msg_zh, msg_en, level = "info") {
@ -628,25 +779,41 @@ wss.on('connection', function (ws) {
// }); // });
console.log("set socket_flowchart at time: ", new Date()); console.log("set socket_flowchart at time: ", new Date());
} else { //其他的ID是用来标识不同的浏览器标签页的 } else { //其他的ID是用来标识不同的浏览器标签页的
await new Promise(resolve => setTimeout(resolve, 2300)); // await new Promise(resolve => setTimeout(resolve, 200));
let handles = await driver.getAllWindowHandles(); let handles = await driver.getAllWindowHandles();
if (arrayDifference(handles, old_handles).length > 0) { if (arrayDifference(handles, old_handles).length > 0) {
old_handles = handles; old_handles = handles;
current_handle = handles[handles.length - 1]; current_handle = handles[handles.length - 1];
await driver.switchTo().window(current_handle);
console.log("New tab opened, change current_handle to: ", current_handle); console.log("New tab opened, change current_handle to: ", current_handle);
// 调整浏览器窗口大小,不然扩展会白屏
let size = await driver.manage().window().getRect();
let width = size.width;
let height = size.height;
await driver.manage().window().setRect({width: width, height: height + 10});
// height = height - 1;
await driver.manage().window().setRect({width: width, height: height});
} }
await new Promise(resolve => setTimeout(resolve, 2000));
handle_pairs[msg.message.id] = current_handle; handle_pairs[msg.message.id] = current_handle;
console.log("Set handle_pair for id: ", msg.message.id, " to ", current_handle, ", title is: ", msg.message.title); console.log("Set handle_pair for id: ", msg.message.id, " to ", current_handle, ", title is: ", msg.message.title);
socket_flowchart.send(JSON.stringify({"type": "title", "data": {"title": msg.message.title}})); socket_flowchart.send(JSON.stringify({"type": "title", "data": {"title": msg.message.title}}));
allWindowSockets.push(ws); allWindowSockets.push(ws);
allWindowScoketNames.push(msg.message.id); allWindowScoketNames.push(msg.message.id);
console.log("set socket for id: ", msg.message.id, " at time: ", new Date()); console.log("set socket for id: ", msg.message.id, " at time: ", new Date());
ws.on('close', function (event) { ws.on('close', async function (event) {
let index = allWindowSockets.indexOf(ws); let index = allWindowSockets.indexOf(ws);
if (index > -1) { if (index > -1) {
allWindowSockets.splice(index, 1); allWindowSockets.splice(index, 1);
allWindowScoketNames.splice(index, 1); allWindowScoketNames.splice(index, 1);
} }
let handles = await driver.getAllWindowHandles();
if (handles.length < old_handles.length) {
old_handles = handles;
current_handle = handles[handles.length - 1];
await driver.switchTo().window(current_handle);
console.log("Current tab closed, change current_handle to: ", current_handle);
}
console.log("socket for id: ", msg.message.id, " closed at time: ", new Date()); console.log("socket for id: ", msg.message.id, " closed at time: ", new Date());
}); });
// console.log("handle_pairs: ", handle_pairs); // console.log("handle_pairs: ", handle_pairs);
@ -671,6 +838,15 @@ async function runBrowser(lang = "en", user_data_folder = '', mobile = false) {
const serviceBuilder = new ServiceBuilder(driverPath); const serviceBuilder = new ServiceBuilder(driverPath);
let options = new chrome.Options(); let options = new chrome.Options();
options.addArguments('--disable-blink-features=AutomationControlled'); options.addArguments('--disable-blink-features=AutomationControlled');
options.addArguments('--disable-infobars');
// 添加实验性选项以排除'enable-automation'开关
options.set('excludeSwitches', ['enable-automation']);
options.excludeSwitches("enable-automation")
// 添加实验性选项来禁用自动化扩展
options.set('useAutomationExtension', false);
// options.addArguments('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36');
options.set
language = lang; language = lang;
if (lang == "en") { if (lang == "en") {
options.addExtensions(path.join(__dirname, "EasySpider_en.crx")); options.addExtensions(path.join(__dirname, "EasySpider_en.crx"));
@ -679,6 +855,7 @@ async function runBrowser(lang = "en", user_data_folder = '', mobile = false) {
} }
options.addExtensions(path.join(__dirname, "XPathHelper.crx")); options.addExtensions(path.join(__dirname, "XPathHelper.crx"));
options.setChromeBinaryPath(chromeBinaryPath); options.setChromeBinaryPath(chromeBinaryPath);
if (user_data_folder != "") { if (user_data_folder != "") {
let dir = path.join(task_server.getDir(), user_data_folder); let dir = path.join(task_server.getDir(), user_data_folder);
console.log(dir); console.log(dir);
@ -790,7 +967,7 @@ app.whenReady().then(() => {
createWindow(); createWindow();
app.on('activate', function () { app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the // On MacOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) { if (BrowserWindow.getAllWindows().length === 0) {
createWindow(); createWindow();

View File

@ -221,7 +221,8 @@
position: sticky; position: sticky;
top: 0px; top: 0px;
margin-bottom: 0px; margin-bottom: 0px;
background-color: azure; /*background-color: azure;*/
background-color: aliceblue;
z-index: 1000; z-index: 1000;
} }

View File

@ -113,10 +113,10 @@
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['maxWaitTime']" type="number" required></input> <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['maxWaitTime']" type="number" required></input>
<label>After executed, whether scroll down:</label> <label>After executed, whether scroll down:</label>
<select v-model='nowNode["parameters"]["scrollType"]' class="form-control"> <select v-model='nowNode["parameters"]["scrollType"]' class="form-control">
<option :vaule = 0>No scrolling</option> <option :value = 0>No scrolling</option>
<option :vaule = 1>Scroll one screen</option> <option :value = 1>Scroll one screen</option>
<option :vaule = 2>Scroll to the end</option> <option :value = 2>Scroll to the end</option>
<option :vaule = 3>Keep scrolling until the page data does not change</option> <option :value = 3>Keep scrolling until the page data does not change</option>
</select> </select>
<label>Scroll Times (the wait time after scrolling <b>ineffective</b> when the scrolling type is set to <b>no scrolling</b>):</label> <label>Scroll Times (the wait time after scrolling <b>ineffective</b> when the scrolling type is set to <b>no scrolling</b>):</label>
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input> <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
@ -157,12 +157,17 @@
<option :value = 0>Selenium</option> <option :value = 0>Selenium</option>
<option :value = 1>JavaScript</option> <option :value = 1>JavaScript</option>
</select> </select>
<label>Open link in new tab:</label>
<select v-model='nowNode["parameters"]["newTab"]' class="form-control">
<option :value = 1>Yes</option>
<option :value = 0>No</option>
</select>
<label>Whether to scroll down after clicking:</label> <label>Whether to scroll down after clicking:</label>
<select v-model='nowNode["parameters"]["scrollType"]' class="form-control"> <select v-model='nowNode["parameters"]["scrollType"]' class="form-control">
<option :vaule = 0>No Scrolling</option> <option :value = 0>No Scrolling</option>
<option :vaule = 1>Scroll one screen</option> <option :value = 1>Scroll one screen</option>
<option :vaule = 2>Scroll to the end</option> <option :value = 2>Scroll to the end</option>
<option :vaule = 3>Keep scrolling until the page data does not change</option> <option :value = 3>Keep scrolling until the page data does not change</option>
</select> </select>
<label>Scroll Times:</label> <label>Scroll Times:</label>
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input> <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
@ -218,10 +223,10 @@
</td> </td>
<td style="width:200px">{{paras.parameters[i-1]["exampleValues"][0]["value"]}}</td> <td style="width:200px">{{paras.parameters[i-1]["exampleValues"][0]["value"]}}</td>
<td> <td>
<a v-on:mousedown="modifyParas(i-1)">Modify</a> <a v-on:mousedown="modifyPara(i-1)">Modify</a>
<a v-on:mousedown="deleteParas(i-1)">Delete</a> <a v-on:mousedown="deletePara(i-1)">Delete</a>
<a v-on:mousedown="upParas(i-1)">Up</a> <a v-on:mousedown="upPara(i-1)">Up</a>
<a v-on:mousedown="downParas(i-1)">Down</a> <a v-on:mousedown="downPara(i-1)">Down</a>
</td> </td>
</tr> </tr>
</table> </table>
@ -241,6 +246,7 @@
</p> </p>
<div :class="{collapse: true, 'show': paras.parameters[paraIndex]['beforeJS'].length!=0 || paras.parameters[paraIndex]['afterJS'].length!=0}" id="elementAdvanced"> <div :class="{collapse: true, 'show': paras.parameters[paraIndex]['beforeJS'].length!=0 || paras.parameters[paraIndex]['afterJS'].length!=0}" id="elementAdvanced">
<div> <div>
<div><a href="#" v-on:mousedown="trailPara(paraIndex)" style="text-decoration: none">Trail Run</a></div>
<label>Execute a JavaScript script <strong>before</strong> extracting data from this element: </label> <label>Execute a JavaScript script <strong>before</strong> extracting data from this element: </label>
<textarea onkeydown="inputDelete(event)" class="form-control" rows="2" <textarea onkeydown="inputDelete(event)" class="form-control" rows="2"
placeholder='The element should be represented by arguments[0]. Here is an example JavaScript code: arguments[0].innerText = arguments[0].innerText.replace("United States","US"). This code replaces occurrences of "United States" with "US" in the text of the element. Subsequently, when extracting data, you will obtain the replaced value.' v-model='paras.parameters[paraIndex]["beforeJS"]'></textarea> placeholder='The element should be represented by arguments[0]. Here is an example JavaScript code: arguments[0].innerText = arguments[0].innerText.replace("United States","US"). This code replaces occurrences of "United States" with "US" in the text of the element. Subsequently, when extracting data, you will obtain the replaced value.' v-model='paras.parameters[paraIndex]["beforeJS"]'></textarea>
@ -414,8 +420,8 @@ This option is an advanced feature that allows directly returning the expression
Please note that this feature does not support assigning values to variables. In other words, you cannot write something like `self.myVar = 1`. If you want to perform assignment operations, please select the previous option, "Run Python code on current environment (the "exec" operation)"</pre> Please note that this feature does not support assigning values to variables. In other words, you cannot write something like `self.myVar = 1`. If you want to perform assignment operations, please select the previous option, "Run Python code on current environment (the "exec" operation)"</pre>
<p style="margin-top: 15px">Whether to record the output/return value of the execution as a field: </p> <p style="margin-top: 15px">Whether to record the output/return value of the execution as a field: </p>
<p><select v-model='nowNode["parameters"]["recordASField"]' class="form-control"> <p><select v-model='nowNode["parameters"]["recordASField"]' class="form-control">
<option :vaule = 0>No</option> <option :value = 0>No</option>
<option :vaule = 1>Yes</option> <option :value = 1>Yes</option>
</select></p> </select></p>
<p><label>Convert parameter type to:</label> <p><label>Convert parameter type to:</label>
<select v-model='nowNode["parameters"]["paraType"]' class="form-control"> <select v-model='nowNode["parameters"]["paraType"]' class="form-control">
@ -490,10 +496,10 @@ Please note that this feature does not support assigning values to variables. In
<div v-if="!useLoop"> <div v-if="!useLoop">
<p>Option switch Mode</p> <p>Option switch Mode</p>
<select class="form-control" v-model='nowNode["parameters"]["optionMode"]'> <select class="form-control" v-model='nowNode["parameters"]["optionMode"]'>
<option :vaule = 0>Switch to the next option</option> <option :value = 0>Switch to the next option</option>
<option :vaule = 1>Switch options by index (0 is the first option)</option> <option :value = 1>Switch options by index (0 is the first option)</option>
<option :vaule = 2>Switch options by option value</option> <option :value = 2>Switch options by option value</option>
<option :vaule = 3>Switch options by option text</option> <option :value = 3>Switch options by option text</option>
</select> </select>
<p>Set value (not applicable for "Switch to the next option" mode)</p> <p>Set value (not applicable for "Switch to the next option" mode)</p>
<input class="form-control" id="selectValue" v-model='nowNode["parameters"]["optionValue"]' autoFocus="autofocus" type="text"></input> <input class="form-control" id="selectValue" v-model='nowNode["parameters"]["optionValue"]' autoFocus="autofocus" type="text"></input>
@ -519,7 +525,7 @@ Please note that this feature does not support assigning values to variables. In
<p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>Operation is in iframe</p> <p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>Operation is in iframe</p>
<!-- 循环选项 --> <!-- 循环选项 -->
<label>Loop Type:</label> <label>Loop Type:</label>
<select v-model='loopType' class="form-control"> <select v-model='loopType' class="form-control" @change="handleLoopTypeChange">
<option :value = 0>Single Element</option> <option :value = 0>Single Element</option>
<option :value = 1>Unfixed Element List</option> <option :value = 1>Unfixed Element List</option>
<option :value = 2>Fixed Element List</option> <option :value = 2>Fixed Element List</option>
@ -568,9 +574,9 @@ If the expression returns a value greater than 0 or evaluates to True, the loop
<div> <div>
<p><label>(Advanced Operation) Define loop exit condition using code/script; or you can add a <b>Custom Action</b>, then select the "Exit Loop" option:</label></p> <p><label>(Advanced Operation) Define loop exit condition using code/script; or you can add a <b>Custom Action</b>, then select the "Exit Loop" option:</label></p>
<select v-model='nowNode["parameters"]["breakMode"]' class="form-control" style="font-weight: bold"> <select v-model='nowNode["parameters"]["breakMode"]' class="form-control" style="font-weight: bold">
<option :vaule = 0>Do not set script (even if a script is written below, it will not be executed)</option> <option :value = 0>Do not set script (even if a script is written below, it will not be executed)</option>
<option :vaule = 1>JavaScript script (start with 'return ')</option> <option :value = 1>JavaScript script (start with 'return ')</option>
<option :vaule = 2>Operating system-level command</option> <option :value = 2>Operating system-level command</option>
</select> </select>
<div> <div>
<textarea style="margin-top: 10px" onkeydown="inputDelete(event)" class="form-control" rows="2" <textarea style="margin-top: 10px" onkeydown="inputDelete(event)" class="form-control" rows="2"
@ -584,10 +590,10 @@ If the expression returns a value greater than 0 or evaluates to True, the loop
<input onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["historyWait"]'></input> <input onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["historyWait"]'></input>
<label>After executed, whether scroll down:</label> <label>After executed, whether scroll down:</label>
<select v-model='nowNode["parameters"]["scrollType"]' class="form-control"> <select v-model='nowNode["parameters"]["scrollType"]' class="form-control">
<option :vaule = 0>No Scrolling</option> <option :value = 0>No Scrolling</option>
<option :vaule = 1>Scroll one screen</option> <option :value = 1>Scroll one screen</option>
<option :vaule = 2>Scroll to the end</option> <option :value = 2>Scroll to the end</option>
<option :vaule = 3>Keep scrolling until the page data does not change</option> <option :value = 3>Keep scrolling until the page data does not change</option>
</select> </select>
<label>Scroll Times:</label> <label>Scroll Times:</label>
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input> <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
@ -596,14 +602,14 @@ If the expression returns a value greater than 0 or evaluates to True, the loop
</div> </div>
<div class="elements" v-if="nodeType==9"> <div class="elements" v-if="nodeType==9">
<label>The conditions are evaluated from left to right, which means if the condition in the leftmost branch is satisfied, the operations within that branch are executed. Otherwise, the condition in the next branch from left to right is evaluated, and so on.</label> <label>The conditions are evaluated from left to right, which means if the condition in the leftmost branch is satisfied, the operations within that branch are executed. Otherwise, the condition in the next branch from left to right is evaluated, and so on. Clicking on a branch while designing tasks allows for <b>dynamic debugging</b> in the browser to verify if the branch satisfies the condition (not applicable to system commands and Python Eval operations). </label>
</div> </div>
<div class="elements" v-if="nodeType==10"> <div class="elements" v-if="nodeType==10">
<label>The conditions are evaluated from left to right, which means if the condition in the leftmost branch is satisfied, the operations within that branch are executed. Otherwise, the condition in the next branch from left to right is evaluated, and so on.</label> <label>The conditions are evaluated from left to right, which means if the condition in the leftmost branch is satisfied, the operations within that branch are executed. Otherwise, the condition in the next branch from left to right is evaluated, and so on. Clicking on a branch while designing tasks allows for <b>dynamic debugging</b> in the browser to verify if the branch satisfies the condition (not applicable to system commands and Python Eval operations). </label>
<p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>Operation is in iframe</p> <p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>Operation is in iframe</p>
<label>Condition Type:</label> <label>Condition Type:</label>
<select v-model='TClass' class="form-control"> <select v-model='TClass' class="form-control" @change="handleJudgeTypeChange">
<option :value = 0>No Condition</option> <option :value = 0>No Condition</option>
<option :value = 1>Text inside current page</option> <option :value = 1>Text inside current page</option>
<option :value = 2>Element inside current page</option> <option :value = 2>Element inside current page</option>
@ -653,8 +659,8 @@ If the expression returns a value greater than 0 or evaluates to True, the opera
<input onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["wait"]'></input> <input onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["wait"]'></input>
<label>Wait Type</label> <label>Wait Type</label>
<select v-model='list.nl[index.nowNodeIndex]["parameters"]["waitType"]' class="form-control"> <select v-model='list.nl[index.nowNodeIndex]["parameters"]["waitType"]' class="form-control">
<option :vaule = 0>Fixed wait (set to wait for 10 seconds then it will wait for 10 seconds)</option> <option :value = 0>Fixed wait (set to wait for 10 seconds then it will wait for 10 seconds)</option>
<option :vaule = 1>Random wait (set to wait for 10 seconds then it will randomly wait for 10 × 0.5 - 10 × 1.5 seconds)</option> <option :value = 1>Random wait (set to wait for 10 seconds then it will randomly wait for 10 × 0.5 - 10 × 1.5 seconds)</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -92,12 +92,12 @@ let app = new Vue({
}, },
loopType: { //循环类型发生变化的时候更新参数值 loopType: { //循环类型发生变化的时候更新参数值
handler: function (newVal, oldVal) { handler: function (newVal, oldVal) {
this.nowNode["parameters"]["loopType"] = newVal; // this.nowNode["parameters"]["loopType"] = newVal;
} }
}, },
TClass: { TClass: {
handler: function (newVal, oldVal) { handler: function (newVal, oldVal) {
this.nowNode["parameters"]["class"] = newVal; // this.nowNode["parameters"]["class"] = newVal;
} }
}, },
useLoop: { useLoop: {
@ -122,7 +122,7 @@ let app = new Vue({
} }
}, },
methods: { methods: {
handleCodeModeChange: function (value) { handleCodeModeChange: function () {
// if (this.codeMode == undefined || this.codeMode == null || this.codeMode == -1) { // if (this.codeMode == undefined || this.codeMode == null || this.codeMode == -1) {
// return; // return;
// } // }
@ -166,6 +166,73 @@ let app = new Vue({
break; break;
} }
}, },
handleLoopTypeChange: function () {
this.nowNode["parameters"]["loopType"] = this.loopType;
switch (parseInt(this.loopType)) {
case 0:
this.nowNode["title"] = LANG("循环 - 单个元素", "Loop - Single Element");
break;
case 1:
this.nowNode["title"] = LANG("循环 - 不固定元素列表", "Loop - Dynamic Element List");
break;
case 2:
this.nowNode["title"] = LANG("循环 - 固定元素列表", "Loop - Fixed Element List");
break;
case 3:
this.nowNode["title"] = LANG("循环 - 文本列表", "Loop - Text List");
break;
case 4:
this.nowNode["title"] = LANG("循环 - 网址列表", "Loop - URL List");
break;
case 5:
this.nowNode["title"] = LANG("循环 - JavaScript命令返回值", "Loop - JavaScript Command Return Value");
break;
case 6:
this.nowNode["title"] = LANG("循环 - 系统命令返回值", "Loop - OS Command Return Value");
break;
case 7:
this.nowNode["title"] = LANG("循环 - Python表达式返回值", "Loop - Python Expression Evaluation Value");
break;
default:
this.nowNode["title"] = LANG("循环", "Loop");
break;
}
},
handleJudgeTypeChange: function () {
this.nowNode["parameters"]["class"] = this.TClass;
switch (parseInt(this.TClass)) {
case 0:
this.nowNode["title"] = LANG("无条件", "No Condition");
break;
case 1:
this.nowNode["title"] = LANG("当前页面包含文本", "Current Page Contains Text");
break;
case 2:
this.nowNode["title"] = LANG("当前页面包含元素", "Current Page Contains Element");
break;
case 3:
this.nowNode["title"] = LANG("当前循环项包含文本", "Current Loop Item Contains Text");
break;
case 4:
this.nowNode["title"] = LANG("当前循环项包含元素", "Current Loop Item Contains Element");
break;
case 5:
this.nowNode["title"] = LANG("JavaScript命令返回值", "JavaScript Command Return Value");
break;
case 6:
this.nowNode["title"] = LANG("系统命令返回值", "OS Command Return Value");
break;
case 7:
this.nowNode["title"] = LANG("针对当前循环项的JavaScript命令返回值", "JavaScript Command Return Value for Current Loop Item");
break;
case 8:
this.nowNode["title"] = LANG("执行环境下的Python表达式值", "Python Expression Evaluation Value");
break;
default:
this.nowNode["title"] = LANG("条件分支", "Condition");
break;
}
},
getCookies: function () { //获取cookies getCookies: function () { //获取cookies
let command = new WebSocket("ws://localhost:" + getUrlParam("wsport")) let command = new WebSocket("ws://localhost:" + getUrlParam("wsport"))
command.onopen = function () { command.onopen = function () {
@ -216,30 +283,38 @@ let app = new Vue({
$("#app > div.elements > div.toolkitcontain > table.toolkittb4 > tbody > tr:last-child")[0].scrollIntoView(false); //滚动到底部 $("#app > div.elements > div.toolkitcontain > table.toolkittb4 > tbody > tr:last-child")[0].scrollIntoView(false); //滚动到底部
}, 200); }, 200);
}, },
modifyParas: function (i) { //修改第i个参数 modifyPara: function (i) { //修改第i个参数
this.paraIndex = i; this.paraIndex = i;
console.log(this.paras); let clone_node = DeepClone(this.nowNode);
clone_node.option = 11; //单独的提取数据参数节点
clone_node.parameters.index = i;
trailElement(clone_node, 0);
}, },
deleteParas: function (i) { //删除第i个参数 trailPara: function (i) { //试运行第i个参数
let clone_node = DeepClone(this.nowNode);
clone_node.option = 11; //单独的提取数据参数节点
clone_node.parameters.index = i;
trailElement(clone_node, 1);
},
deletePara: function (i) { //删除第i个参数
this.nowNode["parameters"]["paras"].splice(i, 1); this.nowNode["parameters"]["paras"].splice(i, 1);
//如果参数删除完了,就把提取数据也删掉 //如果参数删除完了,就把提取数据也删掉
if (this.nowNode["parameters"]["paras"].length == 0) { if (this.nowNode["parameters"]["paras"].length == 0) {
deleteElement(); deleteElement();
} }
}, },
upParas: function (i) { //上移第i个参数 upPara: function (i) { //上移第i个参数
if (i != 0) { if (i != 0) {
let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0]; let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0];
this.nowNode["parameters"]["paras"].splice(i - 1, 0, t); this.nowNode["parameters"]["paras"].splice(i - 1, 0, t);
} }
}, },
downParas: function (i) { //下移第i个参数 downPara: function (i) { //下移第i个参数
if (i != this.nowNode["parameters"]["paras"].length - 1) { if (i != this.nowNode["parameters"]["paras"].length - 1) {
let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0]; let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0];
this.nowNode["parameters"]["paras"].splice(i + 1, 0, t); this.nowNode["parameters"]["paras"].splice(i + 1, 0, t);
} }
}, },
getType: function (nodeType, contentType) { //根据类型得到字段名称 getType: function (nodeType, contentType) { //根据类型得到字段名称
if (contentType == 2) { if (contentType == 2) {
return "InnerHTML"; return "InnerHTML";
@ -283,20 +358,20 @@ function newNode(node) {
} else if (type == 1) //循环 } else if (type == 1) //循环
{ {
return `<div class="loop clk" data="${id}" draggable="true" dataType=${type} id = "${id}" position=${node["position"]} pId=${node["parentId"]}> return `<div class="loop clk" data="${id}" draggable="true" dataType=${type} id = "${id}" position=${node["position"]} pId=${node["parentId"]}>
<p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p> <p style="background:#d6d6d6;text-align:left;padding: 2px 2px 2px 5px">${title}</p>
<p class="arrow" draggable="true" position=-1 data = "${id}" pId=${id}></p> <p class="arrow" draggable="true" position=-1 data = "${id}" pId=${id}></p>
</div> </div>
<p class="arrow" draggable="true" data = "${id}" position=${node["position"]} pId=${node["parentId"]}></p></div>`; <p class="arrow" draggable="true" data = "${id}" position=${node["position"]} pId=${node["parentId"]}></p></div>`;
} else if (type == 2) //判断 } else if (type == 2) //判断
{ {
return LANG(`<div class="loop clk" draggable="true" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}> return LANG(`<div class="loop clk" draggable="true" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
<p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p> <p style="background:#d6d6d6;text-align:left;padding: 2px 2px 2px 5px">${title}</p>
<p class="branchAdd" data="${id}">点击此处在最右边增加条件分支</p> <p class="branchAdd" data="${id}">点击此处在最右边增加条件分支</p>
<div class="judge" id = "${id}"> <div class="judge" id = "${id}">
</div></div> </div></div>
<p class="arrow" draggable="true" data = "${id}" position=${node["position"]} pId=${node["parentId"]}></p></div>`, <p class="arrow" draggable="true" data = "${id}" position=${node["position"]} pId=${node["parentId"]}></p></div>`,
`<div class="loop clk" draggable="true" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}> `<div class="loop clk" draggable="true" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
<p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p> <p style="background:#d6d6d6;text-align:left;padding: 2px 2px 2px 5px">${title}</p>
<p class="branchAdd" data="${id}">Click here to add a new condition to the right most</p> <p class="branchAdd" data="${id}">Click here to add a new condition to the right most</p>
<div class="judge" id = "${id}"> <div class="judge" id = "${id}">
</div></div> </div></div>
@ -304,7 +379,7 @@ function newNode(node) {
} else //判断分支 } else //判断分支
{ {
return `<div class="branch clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}> return `<div class="branch clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
<p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p> <p style="background:#d6d6d6;text-align:left;padding: 2px 2px 2px 5px">${title}</p>
<p data = "${id}" class="arrow" draggable="true" position=-1 pId=${id}></p> <p data = "${id}" class="arrow" draggable="true" position=-1 pId=${id}></p>
<div id = "${id}"> <div id = "${id}">
</div></div>`; </div></div>`;
@ -346,27 +421,7 @@ function branchClick(e) {
e.stopPropagation(); //防止冒泡 e.stopPropagation(); //防止冒泡
} }
function elementMousedown(e) { function operationChange(e, theNode) {
if (e.button == 2) //右键点击
{
try {
document.getElementById("contextMenu").remove();
} catch {
}
if (nowNode != null) {
nowNode.style.borderColor = "skyblue";
}
nowNode = this;
vueData.nowNodeIndex = actionSequence[this.getAttribute("data")];
this.style.borderColor = "blue";
handleElement(); //处理元素
}
e.stopPropagation(); //防止冒泡
}
//元素点击事件
function elementClick(e) {
try { try {
document.getElementById("contextMenu").remove(); document.getElementById("contextMenu").remove();
} catch (e) { } catch (e) {
@ -375,23 +430,40 @@ function elementClick(e) {
if (nowNode != null) { if (nowNode != null) {
nowNode.style.borderColor = "skyblue"; nowNode.style.borderColor = "skyblue";
} }
nowNode = this; nowNode = theNode
vueData.nowNodeIndex = actionSequence[this.getAttribute("data")]; vueData.nowNodeIndex = actionSequence[theNode.getAttribute("data")];
this.style.borderColor = "blue"; theNode.style.borderColor = "blue";
handleElement(); //处理元素 handleElement(); //处理元素
trailElement(app._data.nowNode, 0);
e.stopPropagation(); //防止冒泡 e.stopPropagation(); //防止冒泡
} }
function elementMousedown(e) {
if (e.button == 2) //右键点击
{
operationChange(e, this);
}
e.stopPropagation(); //防止冒泡
}
//元素点击事件
function elementClick(e) {
operationChange(e, this);
e.stopPropagation();
}
function elementDblClick(e) { function elementDblClick(e) {
try { try {
let nodeType = app._data.nowNode["option"] let nodeType = app._data.nowNode["option"]
if (nodeType >= 8) { if (nodeType >= 8) {
showInfo(LANG("试运行功能不适用于循环和条件分支操作,请试运行循环或条件分支内部的具体操作,如点击元素。", "The trial run function is not applicable to loop and condition branch operations. Please try to run the specific operations in the loop/condition branch, such as clicking elements.")); if (nodeType == 8) {
showInfo(LANG("试运行功能不适用于循环操作,请试运行循环内部的具体操作,如点击元素。", "The trial run function is not applicable to loop operations. Please try to run the specific operations in the loop, such as clicking elements."));
}
} else { } else {
if(nodeType == 5 && app._data.nowNode["parameters"]["codeMode"] != 0){ if (nodeType == 5 && (app._data.nowNode["parameters"]["codeMode"] != 0 && app._data.nowNode["parameters"]["codeMode"] != 8)) {
showInfo(LANG("试运行自定义操作功能只适用于执行JavaScript操作。", "The trial run custom action function is only applicable to run JavaScript operation.")) showInfo(LANG("试运行自定义操作功能只适用于执行JavaScript和刷新页面操作。", "The trial run custom action function is only applicable to run JavaScript and refresh page operations."));
} else { } else {
trailRun(app._data.nowNode); trailElement(app._data.nowNode, 1);
} }
} }
} catch (e) { } catch (e) {
@ -513,7 +585,7 @@ function toolBoxKernel(e, para = null) {
if (str == "") { if (str == "") {
title += LANG("元素", "Element"); title += LANG("元素", "Element");
} else { } else {
if(window.location.href.indexOf("_CN") != -1){ if (window.location.href.indexOf("_CN") != -1) {
if (str.length > l) { if (str.length > l) {
str = str.substring(0, l) + "..."; str = str.substring(0, l) + "...";
} }
@ -549,7 +621,7 @@ function toolBoxKernel(e, para = null) {
index: l + 1, index: l + 1,
type: 3, type: 3,
option: 10, option: 10,
title: LANG("条件分支1", "Condition 1"), title: LANG("条件", "No Condition"),
sequence: [], sequence: [],
isInLoop: false, isInLoop: false,
}; };
@ -559,7 +631,7 @@ function toolBoxKernel(e, para = null) {
index: l + 2, index: l + 2,
type: 3, type: 3,
option: 10, option: 10,
title: LANG("条件分支2", "Condition 2"), title: LANG("条件", "No Condition"),
sequence: [], sequence: [],
isInLoop: false, isInLoop: false,
}; };

View File

@ -157,12 +157,17 @@
<option :value = 0>Selenium点击</option> <option :value = 0>Selenium点击</option>
<option :value = 1>JavaScript点击</option> <option :value = 1>JavaScript点击</option>
</select> </select>
<label>在新标签页打开超链接:</label>
<select v-model='nowNode["parameters"]["newTab"]' class="form-control">
<option :value = 1></option>
<option :value = 0></option>
</select>
<label>点击后是否向下滚动页面:</label> <label>点击后是否向下滚动页面:</label>
<select v-model='nowNode["parameters"]["scrollType"]' class="form-control"> <select v-model='nowNode["parameters"]["scrollType"]' class="form-control">
<option :vaule = 0>不滚动</option> <option :value = 0>不滚动</option>
<option :vaule = 1>向下滚动一屏</option> <option :value = 1>向下滚动一屏</option>
<option :vaule = 2>滚动到底部</option> <option :value = 2>滚动到底部</option>
<option :vaule = 3>一直滚动直到页面内容无变化(需设置好滚动后的等待时间用于检测页面变化)</option> <option :value = 3>一直滚动直到页面内容无变化(需设置好滚动后的等待时间用于检测页面变化)</option>
</select> </select>
<label>滚动次数(滚动类型设置为<b>不滚动</b><b>一直滚动</b>时请忽略此项):</label> <label>滚动次数(滚动类型设置为<b>不滚动</b><b>一直滚动</b>时请忽略此项):</label>
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input> <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
@ -218,10 +223,10 @@
</td> </td>
<td style="width:200px">{{paras.parameters[i-1]["exampleValues"][0]["value"]}}</td> <td style="width:200px">{{paras.parameters[i-1]["exampleValues"][0]["value"]}}</td>
<td> <td>
<a v-on:mousedown="modifyParas(i-1)">修改</a> <a v-on:mousedown="modifyPara(i-1)">修改</a>
<a v-on:mousedown="deleteParas(i-1)">删除</a> <a v-on:mousedown="deletePara(i-1)">删除</a>
<a v-on:mousedown="upParas(i-1)">上移</a> <a v-on:mousedown="upPara(i-1)">上移</a>
<a v-on:mousedown="downParas(i-1)">下移</a> <a v-on:mousedown="downPara(i-1)">下移</a>
</td> </td>
</tr> </tr>
</table> </table>
@ -241,6 +246,7 @@
</p> </p>
<div :class="{collapse: true, 'show': paras.parameters[paraIndex]['beforeJS'].length!=0 || paras.parameters[paraIndex]['afterJS'].length!=0}" id="elementAdvanced"> <div :class="{collapse: true, 'show': paras.parameters[paraIndex]['beforeJS'].length!=0 || paras.parameters[paraIndex]['afterJS'].length!=0}" id="elementAdvanced">
<div> <div>
<div><a href="#" v-on:mousedown="trailPara(paraIndex)" style="text-decoration: none">试运行</a></div>
<label>提取该元素数据<strong></strong>针对该元素执行一段JavaScript脚本 </label> <label>提取该元素数据<strong></strong>针对该元素执行一段JavaScript脚本 </label>
<textarea onkeydown="inputDelete(event)" class="form-control" rows="2" <textarea onkeydown="inputDelete(event)" class="form-control" rows="2"
placeholder='该元素用arguments[0]来表示示例JS代码arguments[0].innerText = arguments[0].innerText.replace("上海","Shanghai")即实现了将元素文字中的“上海”替换成”Shanghai“的功能然后后续如提取数据时就会提取到替换后的值。' v-model='paras.parameters[paraIndex]["beforeJS"]'></textarea> placeholder='该元素用arguments[0]来表示示例JS代码arguments[0].innerText = arguments[0].innerText.replace("上海","Shanghai")即实现了将元素文字中的“上海”替换成”Shanghai“的功能然后后续如提取数据时就会提取到替换后的值。' v-model='paras.parameters[paraIndex]["beforeJS"]'></textarea>
@ -519,7 +525,7 @@ print(emotlib.emoji()) # 使用其中的函数。
<p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>在iframe内操作</p> <p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>在iframe内操作</p>
<!-- 循环选项 --> <!-- 循环选项 -->
<label>循环类型:</label> <label>循环类型:</label>
<select v-model='loopType' class="form-control"> <select v-model='loopType' class="form-control" @change="handleLoopTypeChange">
<option :value = 0>单个元素(多用于循环点击下一页)</option> <option :value = 0>单个元素(多用于循环点击下一页)</option>
<option :value = 1>不固定元素列表</option> <option :value = 1>不固定元素列表</option>
<option :value = 2>固定元素列表</option> <option :value = 2>固定元素列表</option>
@ -596,14 +602,14 @@ print(emotlib.emoji()) # 使用其中的函数。
</div> </div>
<div class="elements" v-if="nodeType==9"> <div class="elements" v-if="nodeType==9">
<label>判断条件是从左往右判断的,即如果最左边的条件分支的判断条件满足,则执行最左边分支内的操作,否则判断从左向右第二个分支的条件是否满足,以此类推。</label> <label>判断条件是从左往右判断的,即如果最左边的条件分支的判断条件满足,则执行最左边分支内的操作,否则判断从左向右第二个分支的条件是否满足,以此类推。设计任务时点击分支即可在浏览器中<b>动态调试</b>分支是否满足不适用于系统命令和Python Eval操作</label>
</div> </div>
<div class="elements" v-if="nodeType==10"> <div class="elements" v-if="nodeType==10">
<label>判断条件是从左往右判断的,即如果最左边的条件分支的判断条件满足,则执行最左边分支内的操作,否则判断从左向右第二个分支的条件是否满足,以此类推。</label> <label>判断条件是从左往右判断的,即如果最左边的条件分支的判断条件满足,则执行最左边分支内的操作,否则判断从左向右第二个分支的条件是否满足,以此类推。设计任务时点击分支即可在浏览器中<b>动态调试</b>分支是否满足不适用于系统命令和Python Eval操作</label>
<p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>在iframe内操作</p> <p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>在iframe内操作</p>
<label>条件类型:</label> <label>条件类型:</label>
<select v-model='TClass' class="form-control"> <select v-model='TClass' class="form-control" @change="handleJudgeTypeChange">
<option :value = 0>无条件</option> <option :value = 0>无条件</option>
<option :value = 1>当前页面包括文本</option> <option :value = 1>当前页面包括文本</option>
<option :value = 2>当前页面包括元素</option> <option :value = 2>当前页面包括元素</option>

View File

@ -8,8 +8,8 @@ let exampleMsg = { //示例消息
} }
} }
console.log(JSON.stringify(exampleMsg)); console.log(JSON.stringify(exampleMsg));
let ws = new WebSocket("ws://localhost:"+getUrlParam("wsport")); let ws = new WebSocket("ws://localhost:" + getUrlParam("wsport"));
ws.onopen = function() { ws.onopen = function () {
// Web Socket 已连接上,使用 send() 方法发送数据 // Web Socket 已连接上,使用 send() 方法发送数据
console.log("已连接"); console.log("已连接");
message = { message = {
@ -20,12 +20,12 @@ ws.onopen = function() {
}; };
this.send(JSON.stringify(message)); this.send(JSON.stringify(message));
}; };
ws.onclose = function() { ws.onclose = function () {
// 关闭 websocket // 关闭 websocket
console.log("连接已关闭..."); console.log("连接已关闭...");
}; };
let old_title = ""; let old_title = "";
ws.onmessage = function(evt) { ws.onmessage = function (evt) {
evt = JSON.parse(evt.data); evt = JSON.parse(evt.data);
console.log(evt); console.log(evt);
if (evt["type"] == "title") { //如果不是特殊处理的话,默认全部是增加元素操作 if (evt["type"] == "title") { //如果不是特殊处理的话,默认全部是增加元素操作
@ -36,9 +36,9 @@ ws.onmessage = function(evt) {
} else if (evt["type"] == "notify") { } else if (evt["type"] == "notify") {
if (evt["level"] == "success") { if (evt["level"] == "success") {
showSuccess(LANG(evt["msg_zh"], evt["msg_en"])); showSuccess(LANG(evt["msg_zh"], evt["msg_en"]));
} else if(evt["level"] == "info"){ } else if (evt["level"] == "info") {
showInfo(LANG(evt["msg_zh"], evt["msg_en"])); showInfo(LANG(evt["msg_zh"], evt["msg_en"]));
} else if(evt["level"] == "error"){ } else if (evt["level"] == "error") {
showError(LANG(evt["msg_zh"], evt["msg_en"])); showError(LANG(evt["msg_zh"], evt["msg_en"]));
} }
} else { } else {
@ -46,20 +46,20 @@ ws.onmessage = function(evt) {
} }
}; };
function changeOutputFormat(para){ function changeOutputFormat(para) {
try{ try {
for(let i=0;i<para["parameters"].length;i++) { for (let i = 0; i < para["parameters"].length; i++) {
let exampleValue = para["parameters"][i]["exampleValues"][0]["value"]; let exampleValue = para["parameters"][i]["exampleValues"][0]["value"];
let len = exampleValue.length; let len = exampleValue.length;
if (len > 20000) { if (len > 20000) {
if($("#outputFormat").val() == "xlsx") { if ($("#outputFormat").val() == "xlsx") {
$("#outputFormat").val("csv"); //如果有一个参数的示例值长度超过20000就默认输出为csv $("#outputFormat").val("csv"); //如果有一个参数的示例值长度超过20000就默认输出为csv
showInfo(LANG("示例值长度超过16000超出Excel单个单元格存储限制已自动切换保存为csv格式。", "The length of the example value exceeds 16000, and the csv save format has been automatically switched.")); showInfo(LANG("示例值长度超过16000超出Excel单个单元格存储限制已自动切换保存为csv格式。", "The length of the example value exceeds 16000, and the csv save format has been automatically switched."));
} }
break; break;
} }
} }
} catch(e){ } catch (e) {
console.log(e); console.log(e);
} }
} }
@ -85,7 +85,10 @@ function handleAddElement(msg) {
addElement(2, msg); addElement(2, msg);
} else if (msg["type"] == "inputText") { } else if (msg["type"] == "inputText") {
addElement(4, msg); addElement(4, msg);
} else if (msg["type"] == "changeOption"){ } else if (msg["type"] == "batchInputText") {
addElement(8, msg);
addElement(4, msg);
} else if (msg["type"] == "changeOption") {
addElement(6, msg); addElement(6, msg);
} else if (msg["type"] == "mouseMove") { } else if (msg["type"] == "mouseMove") {
addElement(7, msg); addElement(7, msg);
@ -110,7 +113,8 @@ function handleAddElement(msg) {
} }
changeOutputFormat(msg); changeOutputFormat(msg);
app._data.paras.parameters = app._data["nowNode"]["parameters"]["paras"]; app._data.paras.parameters = app._data["nowNode"]["parameters"]["paras"];
setTimeout(function(){$("#app > div.elements > div.toolkitcontain > table.toolkittb4 > tbody > tr:last-child")[0].scrollIntoView(false); //滚动到底部 setTimeout(function () {
$("#app > div.elements > div.toolkitcontain > table.toolkittb4 > tbody > tr:last-child")[0].scrollIntoView(false); //滚动到底部
}, 200); }, 200);
} else { } else {
addElement(3, msg); addElement(3, msg);
@ -122,9 +126,9 @@ function handleAddElement(msg) {
addElement(3, msg); addElement(3, msg);
changeOutputFormat(msg); changeOutputFormat(msg);
notifyParameterNum(msg["parameters"].length); //通知浏览器端参数的个数变化 notifyParameterNum(msg["parameters"].length); //通知浏览器端参数的个数变化
} else if(msg["type"] == "GetCookies"){ } else if (msg["type"] == "GetCookies") {
for(let node of nodeList){ for (let node of nodeList) {
if(node["option"] == 1){ if (node["option"] == 1) {
node["parameters"]["cookies"] = msg["message"]; node["parameters"]["cookies"] = msg["message"];
$("#pageCookies").val(msg["message"]); $("#pageCookies").val(msg["message"]);
break; break;
@ -138,18 +142,28 @@ function notifyParameterNum(num) {
let message = { let message = {
type: 3, //消息类型3代表元素增加事件 type: 3, //消息类型3代表元素增加事件
from: 1, //0代表从浏览器到流程图1代表从流程图到浏览器 from: 1, //0代表从浏览器到流程图1代表从流程图到浏览器
message: { "pipe": JSON.stringify({ "type": "update_parameter_num", "value": parameterNum }) } // {}全选{BS}退格 message: {"pipe": JSON.stringify({"type": "update_parameter_num", "value": parameterNum})} // {}全选{BS}退格
}; };
ws.send(JSON.stringify(message)); ws.send(JSON.stringify(message));
} }
function trailRun(node){ function trailElement(node, type = 1) {
// type=0代表标记节点type=1代表试运行
let parentNode = nodeList[actionSequence[node["parentId"]]];
if (node.option == 10) { //条件分支的话,传父元素的父元素
parentNode = nodeList[actionSequence[parentNode["parentId"]]];
}
if (parentNode.option == 10) { //如果父元素是条件分支,传父元素的爷爷元素
parentNode = nodeList[actionSequence[parentNode["parentId"]]];
parentNode = nodeList[actionSequence[parentNode["parentId"]]];
}
let message = { let message = {
type: 4, //消息类型4代表试运行事件 type: 4, //消息类型4代表试运行事件
from: 1, //0代表从浏览器到流程图1代表从流程图到浏览器 from: 1, //0代表从浏览器到流程图1代表从流程图到浏览器
message: { "node": JSON.stringify(node), "parentNode": JSON.stringify(nodeList[actionSequence[node["parentId"]]])} message: {"type": type, "node": JSON.stringify(node), "parentNode": JSON.stringify(parentNode)}
}; };
ws.send(JSON.stringify(message)); ws.send(JSON.stringify(message));
console.log(node);
console.log(message); console.log(message);
} }
@ -165,7 +179,7 @@ function handleElement() {
} else if (app._data["nodeType"] == 3) { } else if (app._data["nodeType"] == 3) {
app._data.paraIndex = 0; //参数索引初始化 app._data.paraIndex = 0; //参数索引初始化
app._data.paras.parameters = app._data["nowNode"]["parameters"]["paras"]; app._data.paras.parameters = app._data["nowNode"]["parameters"]["paras"];
} else if (app._data["nodeType"] == 5){ } else if (app._data["nodeType"] == 5) {
app._data.codeMode = app._data["nowNode"]["parameters"]["codeMode"]; app._data.codeMode = app._data["nowNode"]["parameters"]["codeMode"];
} else if (app._data["nodeType"] == 10) { } else if (app._data["nodeType"] == 10) {
app._data.TClass = app._data["nowNode"]["parameters"]["class"]; app._data.TClass = app._data["nowNode"]["parameters"]["class"];
@ -203,6 +217,7 @@ function addParameters(t) {
t["parameters"]["scrollCount"] = 1; //滚动次数 t["parameters"]["scrollCount"] = 1; //滚动次数
t["parameters"]["scrollWaitTime"] = 1; //滚动后等待时间 t["parameters"]["scrollWaitTime"] = 1; //滚动后等待时间
t["parameters"]["clickWay"] = 0; //点击方式0代表selenium点击1代表js点击 t["parameters"]["clickWay"] = 0; //点击方式0代表selenium点击1代表js点击
t["parameters"]["newTab"] = 1; //是否新标签页打开
t["parameters"]["maxWaitTime"] = 10; //最长等待时间 t["parameters"]["maxWaitTime"] = 10; //最长等待时间
t["parameters"]["paras"] = []; //默认参数列表 t["parameters"]["paras"] = []; //默认参数列表
t["parameters"]["wait"] = 2; //点击后等待时间默认2s t["parameters"]["wait"] = 2; //点击后等待时间默认2s
@ -222,7 +237,7 @@ function addParameters(t) {
t["parameters"]["beforeJSWaitTime"] = 0; //执行前js等待时间 t["parameters"]["beforeJSWaitTime"] = 0; //执行前js等待时间
t["parameters"]["afterJS"] = ""; //执行后执行的js t["parameters"]["afterJS"] = ""; //执行后执行的js
t["parameters"]["afterJSWaitTime"] = 0; //执行后js等待时间 t["parameters"]["afterJSWaitTime"] = 0; //执行后js等待时间
} else if(t.option == 5) { //自定义操作 } else if (t.option == 5) { //自定义操作
t["title"] = LANG("执行JavaScript", "Run JavaScript"); t["title"] = LANG("执行JavaScript", "Run JavaScript");
t["parameters"]["clear"] = 0; //清空其他字段数据 t["parameters"]["clear"] = 0; //清空其他字段数据
t["parameters"]["newLine"] = 1; //生成新行 t["parameters"]["newLine"] = 1; //生成新行
@ -246,6 +261,7 @@ function addParameters(t) {
t["parameters"]["optionValue"] = ""; //下拉值 t["parameters"]["optionValue"] = ""; //下拉值
t["parameters"]["index"] = 0; //输入框索引 t["parameters"]["index"] = 0; //输入框索引
} else if (t.option == 8) { //循环 } else if (t.option == 8) { //循环
t["title"] = LANG("循环 - 单个元素", "Loop - single element");
t["parameters"]["scrollType"] = 0; //滚动类型0不滚动1向下滚动1屏2滚动到底部 t["parameters"]["scrollType"] = 0; //滚动类型0不滚动1向下滚动1屏2滚动到底部
t["parameters"]["scrollCount"] = 1; //滚动次数 t["parameters"]["scrollCount"] = 1; //滚动次数
t["parameters"]["scrollWaitTime"] = 1; //滚动后等待时间 t["parameters"]["scrollWaitTime"] = 1; //滚动后等待时间
@ -262,7 +278,7 @@ function addParameters(t) {
t["parameters"]["breakCode"] = ""; //break条件 t["parameters"]["breakCode"] = ""; //break条件
t["parameters"]["breakCodeWaitTime"] = 0; //break条件等待时间 t["parameters"]["breakCodeWaitTime"] = 0; //break条件等待时间
} else if (t.option == 9) { //条件 } else if (t.option == 9) { //条件
t["title"] = LANG("判断条件 - 从左往右依次判断", "Judgment condition - judge from left to right");
} else if (t.option == 10) { //条件分支 } else if (t.option == 10) { //条件分支
t["parameters"]["class"] = 0; //0代表什么条件都没有1代表当前页面包括文本2代表当前页面包括元素3代表当前循环包括文本4代表当前循环包括元素 t["parameters"]["class"] = 0; //0代表什么条件都没有1代表当前页面包括文本2代表当前页面包括元素3代表当前循环包括文本4代表当前循环包括元素
t["parameters"]["value"] = ""; //相关值 t["parameters"]["value"] = ""; //相关值
@ -305,12 +321,13 @@ function modifyParameters(t, para) {
t["parameters"]["value"] = para["value"]; t["parameters"]["value"] = para["value"];
t["parameters"]["xpath"] = para["xpath"]; t["parameters"]["xpath"] = para["xpath"];
t["parameters"]["allXPaths"] = para["allXPaths"]; t["parameters"]["allXPaths"] = para["allXPaths"];
} else if(t.option == 6){ t["parameters"]["useLoop"] = para["useLoop"];
} else if (t.option == 6) {
t["parameters"]["xpath"] = para["xpath"]; t["parameters"]["xpath"] = para["xpath"];
t["parameters"]["allXPaths"] = para["allXPaths"]; t["parameters"]["allXPaths"] = para["allXPaths"];
t["parameters"]["optionMode"] = para["optionMode"]; t["parameters"]["optionMode"] = para["optionMode"];
t["parameters"]["optionValue"] = para["optionValue"]; t["parameters"]["optionValue"] = para["optionValue"];
} else if(t.option == 7){ } else if (t.option == 7) {
t["parameters"]["xpath"] = para["xpath"]; t["parameters"]["xpath"] = para["xpath"];
t["parameters"]["useLoop"] = para["useLoop"]; t["parameters"]["useLoop"] = para["useLoop"];
t["parameters"]["allXPaths"] = para["allXPaths"]; t["parameters"]["allXPaths"] = para["allXPaths"];
@ -318,6 +335,7 @@ function modifyParameters(t, para) {
t["parameters"]["loopType"] = para["loopType"]; t["parameters"]["loopType"] = para["loopType"];
t["parameters"]["xpath"] = para["xpath"]; t["parameters"]["xpath"] = para["xpath"];
t["parameters"]["allXPaths"] = para["allXPaths"]; t["parameters"]["allXPaths"] = para["allXPaths"];
t["parameters"]["textList"] = para["value"];
if (para["nextPage"]) { //循环点击下一页的情况下 if (para["nextPage"]) { //循环点击下一页的情况下
t["title"] = LANG("循环点击下一页", "Loop click next page"); t["title"] = LANG("循环点击下一页", "Loop click next page");
} else if (para["type"] == "loopClickSingle") { //循环点击单个元素 } else if (para["type"] == "loopClickSingle") { //循环点击单个元素
@ -326,8 +344,10 @@ function modifyParameters(t, para) {
t["title"] = LANG("循环点击每个元素", "Loop click every element"); t["title"] = LANG("循环点击每个元素", "Loop click every element");
} else if (para["type"] == "loopMouseMove") { //循环移动到单个元素 } else if (para["type"] == "loopMouseMove") { //循环移动到单个元素
t["title"] = LANG("循环移动到每个元素", "Loop move to every element"); t["title"] = LANG("循环移动到每个元素", "Loop move to every element");
} else if (para["type"] == "multiCollectWithPattern"){ } else if (para["type"] == "multiCollectWithPattern") {
t["title"] = LANG("循环采集数据", "Loop collect data"); t["title"] = LANG("循环采集数据", "Loop collect data");
} else if (para["type"] == "batchInputText") {
t["title"] = LANG("循环输入文字", "Loop input text");
} else { } else {
t["title"] = LANG("循环", "Loop"); t["title"] = LANG("循环", "Loop");
} }
@ -343,24 +363,26 @@ function modifyParameters(t, para) {
} }
} }
function showSuccess(msg, time=4000) { function showSuccess(msg, time = 4000) {
$("#tip").text(msg); $("#tip").text(msg);
$("#tip").slideDown(); //提示框 $("#tip").slideDown(); //提示框
let fadeout = setTimeout(function() { let fadeout = setTimeout(function () {
$("#tip").slideUp(); $("#tip").slideUp();
}, time); }, time);
} }
function showInfo(msg, time=4000) {
function showInfo(msg, time = 4000) {
$("#info_message").text(msg); $("#info_message").text(msg);
$("#tipInfo").slideDown(); //提示框 $("#tipInfo").slideDown(); //提示框
let fadeout = setTimeout(function() { let fadeout = setTimeout(function () {
$("#tipInfo").slideUp(); $("#tipInfo").slideUp();
}, time); }, time);
} }
function showError(msg, time=4000) {
function showError(msg, time = 4000) {
$("#error_message").text(msg); $("#error_message").text(msg);
$("#tipError").slideDown(); //提示框 $("#tipError").slideDown(); //提示框
let fadeout = setTimeout(function() { let fadeout = setTimeout(function () {
$("#tipError").slideUp(); $("#tipError").slideUp();
}, time); }, time);
} }
@ -370,11 +392,11 @@ function showError(msg, time=4000) {
$("#confirm").mousedown(updateUI); $("#confirm").mousedown(updateUI);
//点击保存任务按钮时的处理 //点击保存任务按钮时的处理
$("#saveButton").mousedown(function() { $("#saveButton").mousedown(function () {
saveService(0); saveService(0);
}); });
//点击另存为任务按钮时的处理 //点击另存为任务按钮时的处理
$("#saveAsButton").mousedown(function() { $("#saveAsButton").mousedown(function () {
saveService(1); saveService(1);
}); });
@ -423,7 +445,7 @@ function saveService(type) {
nodeId: i, //记录操作位于的节点位置,重要!!! nodeId: i, //记录操作位于的节点位置,重要!!!
nodeName: nodeList[i]["title"], nodeName: nodeList[i]["title"],
value: nodeList[i]["parameters"]["links"], value: nodeList[i]["parameters"]["links"],
desc: LANG("要采集的网址列表,多行以\\n分开","List of URLs to be collected, separated by \\n for multiple lines",), desc: LANG("要采集的网址列表,多行以\\n分开", "List of URLs to be collected, separated by \\n for multiple lines",),
type: "text", type: "text",
exampleValue: nodeList[i]["parameters"]["links"] exampleValue: nodeList[i]["parameters"]["links"]
}); });
@ -438,7 +460,7 @@ function saveService(type) {
name: "inputText_" + inputIndex++, name: "inputText_" + inputIndex++,
nodeName: nodeList[i]["title"], nodeName: nodeList[i]["title"],
nodeId: i, nodeId: i,
desc: LANG("要输入的文本,如京东搜索框输入:电脑","The text to be entered, such as 'computer' at eBay search box"), desc: LANG("要输入的文本,如京东搜索框输入:电脑", "The text to be entered, such as 'computer' at eBay search box"),
type: "text", type: "text",
exampleValue: nodeList[i]["parameters"]["value"], exampleValue: nodeList[i]["parameters"]["value"],
value: nodeList[i]["parameters"]["value"], value: nodeList[i]["parameters"]["value"],
@ -463,7 +485,7 @@ function saveService(type) {
name: "loopTimes_" + nodeList[i]["title"] + "_" + inputIndex++, name: "loopTimes_" + nodeList[i]["title"] + "_" + inputIndex++,
nodeId: i, nodeId: i,
nodeName: nodeList[i]["title"], nodeName: nodeList[i]["title"],
desc: LANG("循环" + nodeList[i]["title"] + "执行的次数0代表无限循环", "Number of loop executions for loop "+nodeList[i]["title"]+", 0 means unlimited loops (until element not found)"), desc: LANG("循环" + nodeList[i]["title"] + "执行的次数0代表无限循环", "Number of loop executions for loop " + nodeList[i]["title"] + ", 0 means unlimited loops (until element not found)"),
type: "int", type: "int",
exampleValue: nodeList[i]["parameters"]["exitCount"], exampleValue: nodeList[i]["parameters"]["exitCount"],
value: nodeList[i]["parameters"]["exitCount"], value: nodeList[i]["parameters"]["exitCount"],
@ -501,7 +523,7 @@ function saveService(type) {
outputParameters.push({ outputParameters.push({
id: id, id: id,
name: title, name: title,
desc: LANG("自定义操作返回的数据","Output of custom action"), desc: LANG("自定义操作返回的数据", "Output of custom action"),
type: nodeList[i]["parameters"]["paraType"], type: nodeList[i]["parameters"]["paraType"],
recordASField: nodeList[i]["parameters"]["recordASField"], recordASField: nodeList[i]["parameters"]["recordASField"],
exampleValue: "", exampleValue: "",
@ -539,29 +561,37 @@ function saveService(type) {
"outputParameters": outputParameters, "outputParameters": outputParameters,
"graph": nodeList, //图结构要存储下来 "graph": nodeList, //图结构要存储下来
}; };
if(serviceInfo.outputFormat=="mysql"){ if (serviceInfo.outputFormat == "mysql") {
if(!isValidMySQLTableName(serviceInfo.saveName)) { if (!isValidMySQLTableName(serviceInfo.saveName)) {
$('#myModal').modal('hide'); $('#myModal').modal('hide');
showError(LANG("提示保存名不符合MySQL表名规范请重试","The save name is not valid for MySQL table name!")); showError(LANG("提示保存名不符合MySQL表名规范请重试", "The save name is not valid for MySQL table name!"));
return; return;
} }
} }
$.post(backEndAddressServiceWrapper + "/manageTask", { paras: JSON.stringify(serviceInfo) }, $.post(backEndAddressServiceWrapper + "/manageTask", {paras: JSON.stringify(serviceInfo)},
function(result) { $("#serviceId").val(parseInt(result)) }); function (result) {
$("#serviceId").val(parseInt(result))
if (type == 1) { //任务另存为
let currentUrl = window.location.href;
let id = getUrlParam("id");
let newUrl = currentUrl.replace("id=" + id, "id=" + result + "&saveAs=1");
window.location.href = newUrl;
}
});
// alert("保存成功!"); // alert("保存成功!");
$('#myModal').modal('hide'); $('#myModal').modal('hide');
showSuccess(LANG("保存成功!","Save successfully!")); showSuccess(LANG("保存成功!", "Save successfully!"));
// } // }
} }
if (sId != null && sId != -1) //加载任务 if (sId != null && sId != -1) //加载任务
{ {
$.get(backEndAddressServiceWrapper + "/queryTask?id=" + sId, function(result) { $.get(backEndAddressServiceWrapper + "/queryTask?id=" + sId, function (result) {
nodeList = result["graph"]; nodeList = result["graph"];
app.$data.list.nl = nodeList; app.$data.list.nl = nodeList;
for(let node of nodeList){ //兼容旧版本 for (let node of nodeList) { //兼容旧版本
if(node["option"] == 1){ if (node["option"] == 1) {
if(!("cookies" in node["parameters"])) { if (!("cookies" in node["parameters"])) {
node["parameters"]["cookies"] = ""; node["parameters"]["cookies"] = "";
} }
} }
@ -570,15 +600,18 @@ if (sId != null && sId != -1) //加载任务
$("#serviceId").val(result["id"]); $("#serviceId").val(result["id"]);
$("#url").val(result["url"]); $("#url").val(result["url"]);
$("#serviceDescription").val(result["desc"]); $("#serviceDescription").val(result["desc"]);
for(let key of Object.keys(result)){ for (let key of Object.keys(result)) {
try{ try {
$("#"+key).val(result[key]); $("#" + key).val(result[key]);
} catch(e){ } catch (e) {
console.log(e); console.log(e);
} }
} }
if(result["version"]!= serviceInfo["version"]){ if (result["version"] != serviceInfo["version"]) {
showInfo(LANG("提示:该任务为" + result["version"] + "版本任务,当前版本为" + serviceInfo["version"] + ",可能存在兼容性问题,请按照当前版本指南设计任务流程以避免任务执行不正常。","This task is designed by EasySpider " + result["version"] + ", current version of EasySpider is " + serviceInfo["version"] + ", there may be compatibility issues, please design the task flow according to the current version guide to avoid abnormal task execution.")); showInfo(LANG("提示:该任务为" + result["version"] + "版本任务,当前版本为" + serviceInfo["version"] + ",可能存在兼容性问题,请按照当前版本指南设计任务流程以避免任务执行不正常。", "This task is designed by EasySpider " + result["version"] + ", current version of EasySpider is " + serviceInfo["version"] + ", there may be compatibility issues, please design the task flow according to the current version guide to avoid abnormal task execution."));
}
if (getUrlParam("saveAs") == 1) {
showSuccess(LANG("另存为成功!", "Save as successfully!"));
} }
refresh(); refresh();
}); });
@ -587,7 +620,7 @@ if (sId != null && sId != -1) //加载任务
} }
function LANG(zh, en) { function LANG(zh, en) {
if(window.location.href.indexOf("_CN") != -1){ if (window.location.href.indexOf("_CN") != -1) {
return zh; return zh;
} else { } else {
return en; return en;

View File

@ -56,7 +56,7 @@
<td style="height: 30px;overflow: hidden; max-width: 200px">{{list[i-1]["url"]}}</td> <td style="height: 30px;overflow: hidden; max-width: 200px">{{list[i-1]["url"]}}</td>
<td style="text-align: left"><a href="javascript:void(0)" v-on:click="browseTask(list[i-1]['id'])">{{"Task Information~任务信息" | lang}}</a></td> <td style="text-align: left"><a href="javascript:void(0)" v-on:click="browseTask(list[i-1]['id'])">{{"Task Information~任务信息" | lang}}</a></td>
<td style="text-align: left;font-weight: bold" v-if="type==3"><a href="javascript:void(0)" v-on:click="modifyTask(list[i-1]['id'],list[i-1]['url'])">{{"Modify Task~修改任务" | lang}}</a></td> <td style="text-align: left;font-weight: bold" v-if="type==3"><a href="javascript:void(0)" v-on:click="modifyTask(list[i-1]['id'],list[i-1]['url'])">{{"Modify Task~修改任务" | lang}}</a></td>
<td style="text-align: left"><a disabled href="javascript:void(0)" v-on:click="deleteTask(list[i-1]['id'])">{{"Delete Task~删除任务" | lang}}</a></td> <td style="text-align: left"><a disabled href="javascript:void(0)" v-on:dblclick="deleteTask(list[i-1]['id'])">{{"Delete Task (Double Click)~删除任务(双击)" | lang}}</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -96,13 +96,13 @@
window.location.href = "taskInfo.html?type="+getUrlParam("type")+"&id=" + id + "&lang="+getUrlParam("lang")+"&wsport="+getUrlParam("wsport")+"&backEndAddressServiceWrapper="+ app.$data.backEndAddressServiceWrapper; //跳转链接 window.location.href = "taskInfo.html?type="+getUrlParam("type")+"&id=" + id + "&lang="+getUrlParam("lang")+"&wsport="+getUrlParam("wsport")+"&backEndAddressServiceWrapper="+ app.$data.backEndAddressServiceWrapper; //跳转链接
}, },
deleteTask: function(id) { deleteTask: function(id) {
let text = "Are you sure to remove the selected task?"; // let text = "Are you sure to remove the selected task?";
if (getUrlParam("lang") == "en"|| getUrlParam("lang")=="") { // if (getUrlParam("lang") == "en"|| getUrlParam("lang")=="") {
text = "Are you sure to remove the selected task?"; // text = "Are you sure to remove the selected task?";
} else if (getUrlParam("lang") == "zh") { // } else if (getUrlParam("lang") == "zh") {
text = "确定要删除选中的任务吗?"; // text = "确定要删除选中的任务吗?";
} // }
if (confirm(text)) { // if (confirm(text)) {
$.get(app.$data.backEndAddressServiceWrapper + "/deleteTask?id=" + id, function(res) { $.get(app.$data.backEndAddressServiceWrapper + "/deleteTask?id=" + id, function(res) {
$.get(app.$data.backEndAddressServiceWrapper + "/queryTasks", function(re) { $.get(app.$data.backEndAddressServiceWrapper + "/queryTasks", function(re) {
result = re.sort(desc); result = re.sort(desc);
@ -110,7 +110,7 @@
}); });
}); });
// alert("Sorry, the task cannot be deleted since the system is a demo system for paper reviewers, please contact the author (naibowang@u.nus.edu) to remove it.") // alert("Sorry, the task cannot be deleted since the system is a demo system for paper reviewers, please contact the author (naibowang@u.nus.edu) to remove it.")
} // }
}, },
} }
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"id":257,"name":"百度一下,你就知道","url":"https://www.baidu.com","links":"https://www.baidu.com","create_time":"12/12/2023, 5:59:29 AM","update_time":"12/12/2023, 5:59:29 AM","version":"0.6.0","saveThreshold":10,"quitWaitTime":60,"environment":0,"maximizeWindow":0,"maxViewLength":15,"recordLog":1,"outputFormat":"xlsx","saveName":"current_time","inputExcel":"","startFromExit":0,"pauseKey":"p","containJudge":true,"desc":"https://www.baidu.com","inputParameters":[{"id":0,"name":"urlList_0","nodeId":1,"nodeName":"打开网页","value":"https://www.baidu.com","desc":"要采集的网址列表,多行以\\n分开","type":"text","exampleValue":"https://www.baidu.com"}],"outputParameters":[],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[1,2,3,6],"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0},"isInLoop":false},{"id":1,"index":1,"parentId":0,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":false,"position":0,"parameters":{"useLoop":false,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"url":"https://www.baidu.com","links":"https://www.baidu.com","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}},{"id":2,"index":2,"parentId":0,"type":0,"option":2,"title":"点击“同志加兄弟...","sequence":[],"isInLoop":false,"position":1,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"//*[@id=\"hotsearch-content-wrapper\"]/li[1]/a[1]/span[2]","iframe":false,"wait":2,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[],"alertHandleType":0,"allXPaths":["/html/body/div[2]/div[1]/div[5]/div[1]/div[1]/div[3]/ul[1]/li[1]/a[1]/span[2]","//span[contains(., '“同志加兄弟”:中越')]","//SPAN[@class='title-content-title']","/html/body/div[last()-5]/div[last()-3]/div[last()-3]/div/div/div/ul/li[last()-5]/a/span"]}},{"id":3,"index":3,"parentId":0,"type":2,"option":9,"title":"判断条件","sequence":[4,5],"isInLoop":false,"position":2,"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0}},{"id":5,"parentId":3,"index":4,"type":3,"option":10,"title":"条件分支1","sequence":[],"isInLoop":false,"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"class":1,"value":"hao123","code":"","waitTime":0},"position":0},{"id":6,"parentId":3,"index":5,"type":3,"option":10,"title":"条件分支2","sequence":[],"isInLoop":false,"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"class":1,"value":"元首","code":"","waitTime":0},"position":1},{"id":4,"index":6,"parentId":0,"type":0,"option":2,"title":"点击\n换一...","sequence":[],"isInLoop":false,"position":3,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"//*[@id=\"head_wrapper\"]","iframe":false,"wait":2,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[],"alertHandleType":0,"allXPaths":["/html/body/div[2]/div[1]/div[5]","//div[contains(., '')]","id(\"head_wrapper\")","//DIV[@class='head_wrapper s-isindex-wrap nologin']","/html/body/div[last()-6]/div[last()-3]/div[last()-3]"]}}]}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"id":259,"name":"","url":"https://mall.espic.com.cn/mall-view/","links":"https://mall.espic.com.cn/mall-view/","create_time":"12/12/2023, 5:26:03 PM","update_time":"12/12/2023, 5:26:03 PM","version":"0.6.0","saveThreshold":10,"quitWaitTime":60,"environment":0,"maximizeWindow":0,"maxViewLength":15,"recordLog":1,"outputFormat":"xlsx","saveName":"current_time","inputExcel":"","startFromExit":0,"pauseKey":"p","containJudge":false,"desc":"https://mall.espic.com.cn/mall-view/","inputParameters":[{"id":0,"name":"urlList_0","nodeId":1,"nodeName":"打开网页","value":"https://mall.espic.com.cn/mall-view/","desc":"要采集的网址列表,多行以\\n分开","type":"text","exampleValue":"https://mall.espic.com.cn/mall-view/"}],"outputParameters":[],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[1],"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0},"isInLoop":false},{"id":1,"index":1,"parentId":0,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":false,"position":0,"parameters":{"useLoop":false,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"url":"https://mall.espic.com.cn/mall-view/","links":"https://mall.espic.com.cn/mall-view/","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"id":267,"name":"京东全球版-专业的综合网上购物商城","url":"https://www.jd.com","links":"https://www.jd.com","create_time":"12/13/2023, 2:14:40 AM","update_time":"12/13/2023, 2:14:40 AM","version":"0.6.0","saveThreshold":10,"quitWaitTime":60,"environment":0,"maximizeWindow":0,"maxViewLength":15,"recordLog":1,"outputFormat":"xlsx","saveName":"current_time","inputExcel":"","startFromExit":0,"pauseKey":"p","containJudge":false,"desc":"https://www.jd.com","inputParameters":[{"id":0,"name":"urlList_0","nodeId":1,"nodeName":"打开网页","value":"https://www.jd.com","desc":"要采集的网址列表,多行以\\n分开","type":"text","exampleValue":"https://www.jd.com"},{"id":1,"name":"inputText_1","nodeName":"输入文字","nodeId":2,"desc":"要输入的文本,如京东搜索框输入:电脑","type":"text","exampleValue":"1","value":"1"}],"outputParameters":[],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[1,2,3],"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0},"isInLoop":false},{"id":1,"index":1,"parentId":0,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":false,"position":0,"parameters":{"useLoop":false,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"url":"https://www.jd.com","links":"https://www.jd.com","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}},{"id":2,"index":2,"parentId":0,"type":0,"option":4,"title":"输入文字","sequence":[],"isInLoop":false,"position":1,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"//*[@id=\"key\"]","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"value":"1","index":0,"allXPaths":["/html/body/div[4]/div[1]/div[2]/div[1]/input[1]","//input[contains(., '')]","id(\"key\")","//INPUT[@class='text']","/html/body/div[last()-6]/div/div[last()-2]/div/input"]}},{"id":3,"index":3,"parentId":0,"type":0,"option":2,"title":"点击元素","sequence":[],"isInLoop":false,"position":2,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"//*[@id=\"search-link\"]/i[1]","iframe":false,"wait":2,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[],"alertHandleType":0,"allXPaths":["/html/body/div[4]/div[1]/div[2]/div[1]/a[1]/i[1]","//i[contains(., '')]","/html/body/div[last()-6]/div/div[last()-2]/div/a/i"]}},{"id":-1,"index":4,"parentId":0,"type":0,"option":2,"title":"点击元素","sequence":[],"isInLoop":false,"position":3,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"//*[@id=\"search-link\"]/i[1]","iframe":false,"wait":2,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[],"alertHandleType":0,"allXPaths":["/html/body/div[4]/div[1]/div[2]/div[1]/a[1]/i[1]","//i[contains(., '')]","/html/body/div[last()-6]/div/div[last()-2]/div/a/i"]}}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@
"justMyCode": false, "justMyCode": false,
// "args": ["--ids", "[7]", "--read_type", "remote", "--headless", "0"] // "args": ["--ids", "[7]", "--read_type", "remote", "--headless", "0"]
// "args": ["--ids", "[9]", "--read_type", "remote", "--headless", "0", "--saved_file_name", "YOUTUBE"] // "args": ["--ids", "[9]", "--read_type", "remote", "--headless", "0", "--saved_file_name", "YOUTUBE"]
"args": ["--ids", "[20]", "--headless", "0", "--user_data", "0", "--keyboard", "0", "args": ["--ids", "[38]", "--headless", "0", "--user_data", "0", "--keyboard", "0",
"--read_type", "remote"] "--read_type", "remote"]
// "args": "--ids '[97]' --user_data 1 --server_address http://localhost:8074 --config_folder '/Users/naibo/Documents/EasySpider/ElectronJS/' --headless 0 --read_type remote --config_file_name config.json --saved_file_name" // "args": "--ids '[97]' --user_data 1 --server_address http://localhost:8074 --config_folder '/Users/naibo/Documents/EasySpider/ElectronJS/' --headless 0 --read_type remote --config_file_name config.json --saved_file_name"
} }

View File

@ -121,7 +121,7 @@ class BrowserThread(Thread):
"will start from the last step, before we already collected", self.startSteps, " items.") "will start from the last step, before we already collected", self.startSteps, " items.")
else: else:
self.print_and_log("此模式下任务ID", self.id, self.print_and_log("此模式下任务ID", self.id,
"将从头开始执行,如果需要从上次退出的步骤开始执行,请在保存任务时设置是否从上次保存位置开始执行为“是”。") "将从头F开始执行,如果需要从上次退出的步骤开始执行,请在保存任务时设置是否从上次保存位置开始执行为“是”。")
self.print_and_log("In this mode, task ID", self.id, self.print_and_log("In this mode, task ID", self.id,
"will start from the beginning, if you want to start from the last step, please set the option 'start from the last step' to 'yes' when saving the task.") "will start from the beginning, if you want to start from the last step, please set the option 'start from the last step' to 'yes' when saving the task.")
stealth_path = driver_path[:driver_path.find( stealth_path = driver_path[:driver_path.find(
@ -325,11 +325,17 @@ class BrowserThread(Thread):
# 如果(不)固定元素列表循环中只有一个提取数据操作,且提取数据操作的提取内容为元素截图,那么可以快速提取 # 如果(不)固定元素列表循环中只有一个提取数据操作,且提取数据操作的提取内容为元素截图,那么可以快速提取
if len(node["sequence"]) == 1 and self.procedure[node["sequence"][0]]["option"] == 3 and (int(node["parameters"]["loopType"]) == 1 or int(node["parameters"]["loopType"]) == 2): if len(node["sequence"]) == 1 and self.procedure[node["sequence"][0]]["option"] == 3 and (int(node["parameters"]["loopType"]) == 1 or int(node["parameters"]["loopType"]) == 2):
paras = self.procedure[node["sequence"][0]]["parameters"]["paras"] paras = self.procedure[node["sequence"][0]]["parameters"]["paras"]
try:
waitElement = self.procedure[node["sequence"][0]]["parameters"]["waitElement"] waitElement = self.procedure[node["sequence"][0]]["parameters"]["waitElement"]
except:
waitElement = ""
if node["parameters"]["iframe"]:
node["parameters"]["quickExtractable"] = False # 如果是iframe那么不可以快速提取
else:
node["parameters"]["quickExtractable"] = True # 先假设可以快速提取 node["parameters"]["quickExtractable"] = True # 先假设可以快速提取
for para in paras: for para in paras:
optimizable = detect_optimizable(para, ignoreWaitElement=False, waitElement=waitElement) optimizable = detect_optimizable(para, ignoreWaitElement=False, waitElement=waitElement)
if para["iframe"]: # 如果是iframe那么不可以快速提取 if para["iframe"] and not para["relative"]: # 如果是iframe那么不可以快速提取
optimizable = False optimizable = False
if not optimizable: # 如果有一个不满足优化条件,那么就不能快速提取 if not optimizable: # 如果有一个不满足优化条件,那么就不能快速提取
node["parameters"]["quickExtractable"] = False node["parameters"]["quickExtractable"] = False
@ -911,7 +917,6 @@ class BrowserThread(Thread):
self.event.wait() # 等待事件结束 self.event.wait() # 等待事件结束
# 对判断条件的处理 # 对判断条件的处理
def judgeExecute(self, node, loopElement, clickPath="", index=0): def judgeExecute(self, node, loopElement, clickPath="", index=0):
executeBranchId = 0 # 要执行的BranchId executeBranchId = 0 # 要执行的BranchId
for i in node["sequence"]: for i in node["sequence"]:
@ -1513,10 +1518,18 @@ class BrowserThread(Thread):
click_way = int(para["clickWay"]) click_way = int(para["clickWay"])
except: except:
click_way = 0 click_way = 0
try:
newTab = int(para["newTab"])
except:
newTab = 1
try: try:
if click_way == 0: # 用selenium的点击方法 if click_way == 0: # 用selenium的点击方法
try: try:
actions = ActionChains(self.browser) # 实例化一个action对象 actions = ActionChains(self.browser) # 实例化一个action对象
if newTab == 1: # 在新标签页打开
# Ctrl + Click
actions.key_down(Keys.CONTROL).click(element).key_up(Keys.CONTROL).perform()
else:
actions.click(element).perform() actions.click(element).perform()
except Exception as e: except Exception as e:
self.browser.execute_script("arguments[0].scrollIntoView();", element) self.browser.execute_script("arguments[0].scrollIntoView();", element)

View File

@ -22,6 +22,8 @@ export var global = {
iframe: false, iframe: false,
}; };
export function isInIframe() { export function isInIframe() {
try { try {
return window.self !== window.parent; return window.self !== window.parent;
@ -261,6 +263,13 @@ export function clearParameters(deal = true) //清空参数列表
global.app._data.selectStatus = false; global.app._data.selectStatus = false;
} }
export function LANG(zh, en) {
if (global.lang == "zh") {
return zh;
} else {
return en;
}
}
function parameterName(value){ function parameterName(value){
if (global.lang == 'zh'){ if (global.lang == 'zh'){
return value; return value;

View File

@ -1,6 +1,7 @@
//实现与后台和流程图部分的交互 //实现与后台和流程图部分的交互
import {getElementXPaths, global, readXPath, isInIframe, clearEl} from "./global.js"; import {getElementXPaths, global, readXPath, isInIframe, clearEl, LANG} from "./global.js";
import {trial, createNotification} from "./trail.js";
global.ws = new WebSocket("ws://localhost:8084"); global.ws = new WebSocket("ws://localhost:8084");
global.ws.onopen = function () { global.ws.onopen = function () {
@ -24,26 +25,30 @@ global.ws.onmessage = function (evt) {
createNotification(LANG(evt["msg_zh"], evt["msg_en"]), evt["level"]); createNotification(LANG(evt["msg_zh"], evt["msg_en"]), evt["level"]);
} else if (evt["type"] == "cancelSelection") { //试运行点击元素后取消选中元素 } else if (evt["type"] == "cancelSelection") { //试运行点击元素后取消选中元素
clearEl(); clearEl();
} else if (evt["type"] == "trial"){
trial(evt);
} }
}; };
function LANG(zh, en) {
if (global.lang == "zh") {
return zh;
} else {
return en;
}
}
export function input(value) {
export function input(value, batch=false) {
let type = "inputText";
let useLoop = false;
if (batch) {
type = "batchInputText";
useLoop = true;
}
let message = { let message = {
"type": "inputText", "type": type,
"history": history.length, //记录history的长度 "history": history.length, //记录history的长度
"tabIndex": -1, "tabIndex": -1,
"xpath": readXPath(global.nodeList[0]["node"], 0), "xpath": readXPath(global.nodeList[0]["node"], 0),
"allXPaths": getElementXPaths(global.nodeList[0]["node"]), "allXPaths": getElementXPaths(global.nodeList[0]["node"]),
"iframe": global.iframe, "iframe": global.iframe,
"value": value, "value": value,
"useLoop": useLoop, //是否使用循环内元素
"loopType": 3, //循环类型3为文本列表
}; };
window.stop(); window.stop();
let message_action = { let message_action = {
@ -171,63 +176,6 @@ export function collectSingle() {
// createNotification(LANG("采集成功", "Collect successfully"), "success"); // createNotification(LANG("采集成功", "Collect successfully"), "success");
} }
function createNotification(text, type="info") {
// 创建通知元素
let notification = document.createElement('div');
notification.className = 'notification_of_easyspider'; // 使用 class 方便后续添加样式
notification.setAttribute("data-timestamp", new Date().getTime()); // 用于清除通知
// 设置通知文本
notification.innerText = text;
// 定义与添加样式
let cssText = `
position: fixed;
bottom: 20px; /* 距底部20px */
right: -320px; /* 初始位置在屏幕右侧假设通知框宽度320px */
min-width: 300px;
padding: 10px 20px;
color: white;
z-index: 2147483641;
border-radius: 4px;
text-align: center;
font-size: 15px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
transition: right 0.5s ease-in-out; /* 动画效果 */
`;
notification.style.cssText = cssText;
if (type === "success") {
notification.style.backgroundColor = 'rgb(103, 194, 58)';
} else if (type === "info") {
notification.style.backgroundColor = '#00a8ff';
} else if (type === "warning") {
notification.style.backgroundColor = 'rgb(230, 162, 60)';
} else if (type === "error") {
notification.style.backgroundColor = '#ff6b6b';
}
// 将通知添加到页面中
document.body.appendChild(notification);
// 触发动画,通知从右向左滑入
setTimeout(function () {
notification.style.right = '20px'; // 调整距离左边的位置
}, 100);
// let removeXPathText = text.split("是否正确:")[0].split("is correct:")[0];
// let timeoutInterval = 1500 * removeXPathText.length / 5;
let timeoutInterval = 1500 * text.length / 5;
// 设置退出动画,通知从右向左滑出
setTimeout(function () {
notification.style.right = '-320px'; // 向左退出
// 确定动画结束后移除通知
notification.addEventListener('transitionend', function () {
if (notification.parentNode === document.body) {
document.body.removeChild(notification); // 避免移除已经不存在的元素
}
});
}, timeoutInterval + 500); // 通知停留时间加上动画时间
}
setInterval(function () { setInterval(function () {
let notifications = document.getElementsByClassName("notification_of_easyspider"); let notifications = document.getElementsByClassName("notification_of_easyspider");
for (let i = 0; i < notifications.length; i++) { for (let i = 0; i < notifications.length; i++) {

View File

@ -36,7 +36,8 @@
v-on:mousedown="selectDescendents">选中子元素</a> <span title="应选尽选模式,如想使用其他模式请先选中全部再选中子元素"></span></div> v-on:mousedown="selectDescendents">选中子元素</a> <span title="应选尽选模式,如想使用其他模式请先选中全部再选中子元素"></span></div>
<div v-if="!selectedDescendents && !selectStatus" id="Single"> <div v-if="!selectedDescendents && !selectStatus" id="Single">
<div v-if="tname()=='选择框'"><a v-on:mousedown="changeSelect">切换下拉选项</a><span title=""></span></div> <div v-if="tname()=='选择框'"><a v-on:mousedown="changeSelect">切换下拉选项</a><span title=""></span></div>
<div v-if="tname()=='文本框'"><a v-on:mousedown="setInput">输入文字</a><span title=""></span></div> <div v-if="tname()=='文本框'"><a v-on:mousedown="setInput(false)">输入文字</a><span title=""></span></div>
<div v-if="tname()=='文本框'"><a v-on:mousedown="setInput(true)">批量输入文字</a><span title=""></span></div>
<div v-if="tname()!='图片'"><a v-on:mousedown="getText">采集该{{ tname() }}的文本</a><span <div v-if="tname()!='图片'"><a v-on:mousedown="getText">采集该{{ tname() }}的文本</a><span
title="采集文本"></span></div> title="采集文本"></span></div>
<div v-if="tname()=='选择框'"><a v-on:mousedown="getSelectedValue">采集当前选中项的值</a><span title=""></span></div> <div v-if="tname()=='选择框'"><a v-on:mousedown="getSelectedValue">采集当前选中项的值</a><span title=""></span></div>
@ -170,10 +171,10 @@
<div><a v-on:mousedown="getCurrentTitle">Collect Title of current page</a><span title="Title of this page"></span></div> <div><a v-on:mousedown="getCurrentTitle">Collect Title of current page</a><span title="Title of this page"></span></div>
<div><a v-on:mousedown="getCurrentURL">Collect URL of current page</a><span title="URL of this page"></span></div> <div><a v-on:mousedown="getCurrentURL">Collect URL of current page</a><span title="URL of this page"></span></div>
</div> </div>
<p style="color:black"> Mouse move to smiling face to see operation help.</p> <p style="color:black; margin-top: 10px"> Mouse move to smiling face to see operation help.</p>
<p style="color:black"> When your mouse moves to the element, please <strong>right-click</strong> your <p style="color:black; margin-top: 10px"> When your mouse moves to the element, please <strong>right-click</strong> your
mouse button or press <strong>F7</strong> on the keyboard to select it.</p> mouse button or press <strong>F7</strong> on the keyboard to select it.</p>
<p style="color:black"> If this toolbox blocks the page element, you can click the × button in the <p style="color:black; margin-top: 10px"> If this toolbox blocks the page element, you can click the × button in the
lower right corner of this toolbox to close it.</p> lower right corner of this toolbox to close it.</p>
<p style="color:black; margin-top: 10px"> When clicked with the left mouse button, the page will also respond, but this click operation will not be recorded in the task flow. Similarly, if you want to input in a text box but do not want the action to be recorded , you can move the mouse to the text box and press <strong>F9</strong> on the keyboard to input.</p> <p style="color:black; margin-top: 10px"> When clicked with the left mouse button, the page will also respond, but this click operation will not be recorded in the task flow. Similarly, if you want to input in a text box but do not want the action to be recorded , you can move the mouse to the text box and press <strong>F9</strong> on the keyboard to input.</p>
<p style="color:black; margin-top: 10px"> If you accidentally left-click on an element and cause the page to jump, simply go back or switch back to the tab.</p> <p style="color:black; margin-top: 10px"> If you accidentally left-click on an element and cause the page to jump, simply go back or switch back to the tab.</p>
@ -193,7 +194,8 @@
<div v-if="!selectedDescendents && !selectStatus" id="Single"> <div v-if="!selectedDescendents && !selectStatus" id="Single">
<!-- <div v-if="tname()=='selection box'"> <a>循环切换该下拉项</a><span title=""></span></div> --> <!-- <div v-if="tname()=='selection box'"> <a>循环切换该下拉项</a><span title=""></span></div> -->
<div v-if="tname()=='选择框'"><a v-on:mousedown="changeSelect">Change selection option</a><span title=""></span></div> <div v-if="tname()=='选择框'"><a v-on:mousedown="changeSelect">Change selection option</a><span title=""></span></div>
<div v-if="tname()=='文本框'"><a v-on:mousedown="setInput">Input Text</a><span title=""></span> <div v-if="tname()=='文本框'"><a v-on:mousedown="setInput(false)">Input Text</a><span title=""></span>
<div v-if="tname()=='文本框'"><a v-on:mousedown="setInput(true)">Input Text (Batch)</a><span title=""></span>
</div> </div>
<div v-if="tname()!='图片'"><a v-on:mousedown="getText">Extract {{ tname() | toEng }}'s text</a><span <div v-if="tname()!='图片'"><a v-on:mousedown="getText">Extract {{ tname() | toEng }}'s text</a><span
title="collect text"></span></div> title="collect text"></span></div>
@ -362,6 +364,7 @@ export default {
el: '#realcontent', el: '#realcontent',
data: { data: {
lang: global.lang, lang: global.lang,
batch: false, //
option: 0, option: 0,
list: {nl: global.nodeList, opp: global.outputParameters}, list: {nl: global.nodeList, opp: global.outputParameters},
valTable: [], // valTable: [], //
@ -578,7 +581,8 @@ export default {
// global.nodeList[0]["node"].click(); // // global.nodeList[0]["node"].click(); //
clearEl(); clearEl();
}, },
setInput: function () { // setInput: function (batch=false) { //
this.batch = batch;
this.page = 1; this.page = 1;
this.$nextTick(function () { // this.$nextTick(function () { //
document.getElementById("WTextBox").focus(); document.getElementById("WTextBox").focus();
@ -591,7 +595,7 @@ export default {
// } else{ // } else{
// global.nodeList[0]["node"].setAttribute("value", ""); // box // global.nodeList[0]["node"].setAttribute("value", ""); // box
// } // }
input(this.text); // input(this.text, this.batch); //
this.text = ""; this.text = "";
clearEl(); clearEl();
}, },

View File

@ -0,0 +1,150 @@
import {LANG} from "./global.js";
//试运行操作标记
export function trial(evt) {
let node = JSON.parse(evt["message"]["message"]["node"]);
let parentNode = JSON.parse(evt["message"]["message"]["parentNode"]);
let parameters = node.parameters;
let type = evt["message"]["message"]["type"];
console.log("parameters", parameters);
if (type == 0) {
let option = node.option;
console.log("option", option);
if (option == 2 || option == 4 || option == 6 || option == 7) {
let xpath = parameters.xpath;
if (parameters.useLoop && option != 4 && option != 6) {
let parentXPath = parentNode.parameters.xpath;
xpath = parentXPath + xpath;
}
let element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
// if (element != null) {
// //移动到元素位置
// element.scrollIntoView({block: "center", inline: "center"});
// }
} else if (option == 10) { //条件分支
let condition = parameters.class;
let result = 0;
let additionalInfo = "";
if (condition == 0) { //无条件
result = 1;
} else if (condition == 1) { //当前页面包含文本
let value = parameters.value;
let element = document.getElementsByTagName("body")[0];
let bodyText = element.innerText;
let outcome = bodyText.indexOf(value) >= 0;
if (outcome) {
result = 1;
}
} else if (condition == 2) { //当前页面包含元素xpath
let xpath = parameters.value;
let element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (element != null) {
result = 1;
}
} else if (condition == 3) { //当前循环项包含文本xpath
let value = parameters.value;
let xpath = parentNode.parameters.xpath;
let element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (element != null) {
let elementText = element.innerText;
let outcome = elementText.indexOf(value) >= 0;
if (outcome) {
result = 1;
}
}
if(result == 0){
additionalInfo = LANG(",注意只会检索第一个匹配到的循环项", ", note that only the first matching loop item will be retrieved");
}
} else if (condition == 4) { //当前循环项包含元素xpath
let xpath = parentNode.parameters.xpath;
let value = parameters.value;
// full_path = "(" + parentPath + ")" + \
// "[" + str(index + 1) + "]" + \
// relativeXPath + content_type
xpath = "(" + xpath + ")" + "[" + "1" + "]" + value;
let element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (element != null) {
result = 1;
}
if(result == 0){
additionalInfo = LANG(",注意只会检索第一个匹配到的循环项", ", note that only the first matching loop item will be retrieved");
}
} else if (condition == 5 || condition == 7) { //从主程序传入的结果
result = evt["message"]["message"]["result"];
if(condition == 7 && result == 0){
additionalInfo = LANG(",注意只会检索第一个匹配到的循环项", ", note that only the first matching loop item will be retrieved");
}
} else {
result = 2;
}
if (result == 0) {
createNotification(LANG("当前页面下,条件分支“" + node.title + "”的条件未满足" + additionalInfo, "The condition of the conditional branch: " + node.title + " is not met on the current page" + additionalInfo), "warning");
} else if (result == 1) {
createNotification(LANG("当前页面下,条件分支“" + node.title + "”的条件已满足" + additionalInfo, "The condition of the conditional branch: " + node.title + " is met on the current page" + additionalInfo), "success");
} else if (result == 2) {
createNotification(LANG("不支持此条件判断类型的动态调试,请在任务正式调用阶段测试是否有效。", "Dynamic debugging of this condition judgment type is not supported. Please test whether it is valid in the formal call stage."), "info");
}
}
}
}
export function createNotification(text, type = "info") {
// 创建通知元素
let notification = document.createElement('div');
notification.className = 'notification_of_easyspider'; // 使用 class 方便后续添加样式
notification.setAttribute("data-timestamp", new Date().getTime()); // 用于清除通知
// 设置通知文本
notification.innerText = text;
// 定义与添加样式
let cssText = `
position: fixed;
bottom: 20px; /* 距底部20px */
right: -320px; /* 初始位置在屏幕右侧假设通知框宽度320px */
min-width: 300px;
padding: 10px 20px;
color: white;
z-index: 2147483641;
border-radius: 4px;
text-align: center;
font-size: 15px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
transition: right 0.5s ease-in-out; /* 动画效果 */
`;
notification.style.cssText = cssText;
if (type === "success") {
notification.style.backgroundColor = 'rgb(103, 194, 58)';
} else if (type === "info") {
notification.style.backgroundColor = '#00a8ff';
} else if (type === "warning") {
notification.style.backgroundColor = 'rgb(230, 162, 60)';
} else if (type === "error") {
notification.style.backgroundColor = '#ff6b6b';
notification.style.bottom = '70px';
}
// 将通知添加到页面中
document.body.appendChild(notification);
// 触发动画,通知从右向左滑入
setTimeout(function () {
notification.style.right = '20px'; // 调整距离左边的位置
}, 100);
// let removeXPathText = text.split("是否正确:")[0].split("is correct:")[0];
// let timeoutInterval = 1500 * removeXPathText.length / 5;
let timeoutInterval = 1500 * text.length / 5;
// 设置退出动画,通知从右向左滑出
setTimeout(function () {
notification.style.right = '-320px'; // 向左退出
// 确定动画结束后移除通知
notification.addEventListener('transitionend', function () {
if (notification.parentNode === document.body) {
document.body.removeChild(notification); // 避免移除已经不存在的元素
}
});
}, timeoutInterval + 500); // 通知停留时间加上动画时间
}

View File

@ -164,7 +164,8 @@
position: sticky; position: sticky;
top: 0px; top: 0px;
margin-bottom: 0px; margin-bottom: 0px;
background-color: azure; /*background-color: azure;*/
background-color: aliceblue;
z-index: 1000; z-index: 1000;
} }