自動化測試中,QTP和selenium IDE都支援瀏覽器錄製與回放功能,簡單的來說就像一個記錄操作步驟的機器人,可以按照記錄的步驟重新執行一遍,這就是指令碼錄製。
個人覺得傳統錄製工具有些弊端,加上要定製支援我自己的自動化框架(python單機版自動化測試框架原始碼),所以自己用JavaScript寫了一個錄製工具,在控制檯列印記錄的python指令碼如下:
JavaScript程式碼如下(初稿,還在不斷除錯完善中):
1 var click_textContent = ' 2 var father_level = 0; 3 var child_ctl = ""; 4 var child_ctl_tmp = ""; 5 var next_focusedElement = null; 6 window.clickedElement = null; 7 8 document.addEventListener("click", function(event) { 9 window.clickedElement = event.target; 10 console.log(window.clickedElement); 11 father_level = 0; 12 myDispose_click(window.clickedElement); 13 }); 14 15 function myDispose_click(focusedElement) { 16 console.log(`開始 父${father_level} -------------------------`); 17 let tag_name = focusedElement.tagName.toLowerCase(); 18 let outerHTML = focusedElement.outerHTML; 19 console.log(outerHTML); 20 21 if (tag_name === "body") { 22 return myDispose_fullXPath(element); 23 } 24 25 let my_all_value = ""; 26 let text = focusedElement.textContent.trim().replace(/"/g, "\\\""); 27 if (text !== "" && !text.includes("\\n")) { 28 my_all_value = `contains(text(),\'${text}\')`; 29 if (myDispose_count_number(text, "text", tag_name)) { 30 let xpath = `//${tag_name}[${my_all_value}]`; 31 let parameter = `driver, By.XPATH, "${xpath}"`; 32 myDispose_success(parameter); 33 return parameter; 34 } 35 } else { 36 text = "" 37 } 38 39 let attributes = focusedElement.attributes; 40 console.log(`屬性名稱列表: ${Array.from(attributes).map(attr => attr.name).join(",")}`); 41 42 for (let i = 0; i < attributes.length; i++) { 43 let attribute_name = attributes[i].name; 44 let attribute_value = attributes[i].value; 45 46 if (attribute_name === "class") { 47 let class_value_list = attribute_value.split(" "); 48 console.log(`class列表:${class_value_list}`); 49 if (class_value_list.includes("focusing")) { 50 class_value_list = class_value_list.filter(value => value !== "focusing"); 51 } 52 53 for (let class_value of class_value_list) { 54 if (class_value === "") { 55 continue; 56 } 57 if (my_all_value === "") { 58 my_all_value = `contains(@class,\'${class_value}\')`; 59 } else { 60 my_all_value += ` and contains(@class,\'${class_value}\')`; 61 } 62 63 if (myDispose_count_number(class_value, attribute_name, tag_name)) { 64 let parameter = `driver, By.CLASS_NAME, "${class_value}"`; 65 myDispose_success(parameter); 66 return parameter; 67 } 68 69 let xpath = `//${tag_name}[${my_all_value}]`; 70 let result = myDispose_count_evaluate(xpath); 71 if (result) { 72 let parameter = `driver, By.XPATH, "${xpath}"`; 73 myDispose_success(parameter); 74 return parameter; 75 } 76 } 77 } else { 78 console.log(`${attribute_name}:${attribute_value}`); 79 /*if (attribute_value === "" || /\d/.test(attribute_value)) {*/ 80 if (attribute_value === "" || (attribute_name !== "src" && attribute_value.match(/[0-9]/))) { 81 continue; 82 } 83 84 if (my_all_value === "") { 85 my_all_value = `contains(@${attribute_name}, \'${attribute_value}\')`; 86 } else { 87 my_all_value += ` and contains(@${attribute_name}, \'${attribute_value}\')`; 88 } 89 90 if (myDispose_count_number(attribute_value, attribute_name, tag_name)) { 91 let xpath = `//${tag_name}[contains(@${attribute_name}, \'${attribute_value}\')]`; 92 let parameter = `driver, By.XPATH, "${xpath}"`; 93 myDispose_success(parameter); 94 return parameter; 95 } 96 97 let xpath = `//${tag_name}[${my_all_value}]`; 98 let result = myDispose_count_evaluate(xpath); 99 if (result) { 100 let parameter = `driver, By.XPATH, "${xpath}"`; 101 myDispose_success(parameter); 102 return parameter; 103 } 104 } 105 } 106 107 if (my_all_value !== "") { 108 let xpath = `//${tag_name}[${my_all_value}]`; 109 let result = myDispose_count_evaluate(xpath); 110 if (result) { 111 let parameter = `driver, By.XPATH, "${xpath}"`; 112 myDispose_success(parameter); 113 return parameter; 114 } else { 115 myDispose_not_unique(focusedElement, xpath); 116 } 117 } else { 118 let father = focusedElement.parentElement; 119 if (father) { 120 if (father.children.length === 1) { 121 let xpath = `//${tag_name}`; 122 myDispose_not_unique(focusedElement, xpath); 123 } else { 124 return myDispose_fullXPath(element); 125 } 126 } else { 127 return null; 128 } 129 } 130 } 131 132 function myDispose_success(parameter) { 133 if (father_level === 0) { 134 console.log(`self.myWtClickEx(${parameter})`); 135 } else { 136 console.log(`father = self.myWtFindElement(${parameter})`); 137 console.log(`self.myWtClickEx(${child_ctl})`); 138 } 139 console.log(`結束 父${father_level} -------------------------`); 140 } 141 142 function myDispose_count_evaluate(xpath) { 143 let elements = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 144 if (father_level === 0) { 145 if (elements.snapshotLength === 1) { 146 return true 147 } else { 148 return null 149 } 150 } else { 151 if (elements.snapshotLength === 1) { 152 let firstElement = elements.snapshotItem(0); 153 let result = document.evaluate(child_ctl_tmp, firstElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 154 if (result.snapshotLength === 1) { 155 return true 156 } else { 157 return null 158 } 159 } else { 160 return null 161 } 162 } 163 } 164 165 function myDispose_count_number(attribute_value, attribute_name, tag_name) { 166 if (attribute_value === "") { 167 return null; 168 }; 169 if (attribute_name !== "text" && attribute_name !== "src" && attribute_value.match(/[0-9]/)) { 170 return null; 171 }; 172 173 let xpath; 174 if (attribute_name !== "text") { 175 xpath = `//${tag_name}[contains(@${attribute_name}, "${attribute_value}")]`; 176 } else { 177 xpath = `//${tag_name}[contains(text(), \'${attribute_value}\')]`; 178 }; 179 180 let result = myDispose_count_evaluate(xpath); 181 182 if (result) { 183 console.log(`${attribute_name}:"${attribute_value}" 在網頁中出現1次`); 184 return true; 185 } else { 186 console.log(`${attribute_name}:"${attribute_value}" 在網頁中出現了多次`); 187 return null; 188 }; 189 } 190 191 function myDispose_not_unique(focusedElement, xpath) { 192 let textStr = `self.myWtClickEx(driver, By.XPATH, "${xpath}")`; 193 console.log("# 不是1"); 194 console.log(textStr); 195 console.log(`結束 父${father_level} -------------------------`); 196 197 if (father_level === 0) { 198 child_ctl = `father, By.XPATH, ".${xpath}"`; 199 child_ctl_tmp = `.${xpath}`; 200 next_focusedElement = focusedElement; 201 } 202 203 let father = focusedElement.parentElement; 204 if (father) { 205 father_level++; 206 myDispose_click(father); 207 } 208 } 209 210 function myDispose_fullXPath(element) { 211 let xpath = getElementfullXPath(element); 212 let elements = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 213 if (elements && elements.snapshotLength === 1) { 214 console.log(`self.myWtClickEx(driver, By.XPATH, "${xpath}")`); 215 console.log("結束:tag名稱為body"); 216 console.log(`結束 父${father_level} -------------------------`); 217 return xpath; 218 } else { 219 console.log("結束:tag名稱為body"); 220 console.log(`結束 父${father_level} -------------------------`); 221 return null; 222 } 223 } 224 225 function getElementfullXPath(element) { 226 if (element && element.id) 227 if (!element.id.match(/[0-9]/)) { 228 return \'//*[@id="\' + element.id + \'"]\'; 229 } 230 231 if (element==null) 232 return ""; 233 234 var index = 0; 235 var loacl_tagName = element.tagName; 236 var sibling = element.previousSibling; 237 var sibling_tagName = null; 238 if (sibling) { 239 sibling_tagName = sibling.tagName; 240 } 241 while (sibling && sibling.nodeType === 1 && loacl_tagName === sibling_tagName) { 242 index++; 243 sibling = sibling.previousSibling; 244 if (sibling) { 245 sibling_tagName = sibling.tagName; 246 } else { 247 sibling_tagName = null; 248 } 249 } 250 251 parent = element.parentNode; 252 if (parent) { 253 var xpath = getElementfullXPath(parent); 254 if (xpath === "undefined") { 255 return ""; 256 } else { 257 if (index === 0) { 258 xpath += "/" + element.tagName.toLowerCase(); 259 } else { 260 xpath += "/" + element.tagName.toLowerCase() + "[" + (index+1) + "]"; 261 } 262 return xpath; 263 } 264 } else { 265 return ""; 266 } 267 } 268 '; 269 270 271 var input_textContent = ' 272 let inputs = document.querySelectorAll(`input[type="text"]`); 273 inputs.forEach(input => { 274 input.addEventListener("input", function(event) { 275 console.log(this.value); 276 let parameter = myDispose_click(event.target); 277 if (parameter !== null) { 278 let textStr = `self.myWtSendKeysWebEx(${parameter}, "${this.value}")`; 279 console.log(textStr); 280 } 281 }); 282 }); 283 '; 284 285 286 /*iframe*/ 287 let iframes = document.getElementsByTagName('iframe'); 288 for (let i = 0; i < iframes.length; i++) { 289 let iframe = iframes[i]; 290 if (iframe.contentWindow && iframe.contentWindow.document) { 291 let script = iframe.contentWindow.document.createElement('script'); 292 script.type = 'text/javascript'; 293 script.textContent = click_textContent; 294 iframe.contentWindow.document.head.appendChild(script); 295 let inputs = iframe.contentWindow.document.querySelectorAll(`input[type="text"]`); 296 inputs.forEach(input => { 297 input.addEventListener("input", function(event) { 298 console.log(this.value); 299 let parameter = myDispose_click(event.target); 300 if (parameter !== null) { 301 let textStr = `self.myWtSendKeysWebEx(${parameter}, "${this.value}")`; 302 console.log(textStr); 303 } 304 }); 305 }); 306 } 307 } 308 309 310 /*非iframe*/ 311 let script = document.createElement('script'); 312 script.type = 'text/javascript'; 313 script.textContent = click_textContent + input_textContent; 314 document.head.appendChild(script);