介面自動化測試錄製工具,讓python selenium自動化測試指令碼開發更加方便

rmticocean發表於2024-07-29

自動化測試中,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);

相關文章