Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import math
2import time
3import urllib.parse
4import yaml
5from selenium.webdriver.support.ui import WebDriverWait
6from selenium.webdriver.support import expected_conditions
7from selenium.webdriver.common.action_chains import ActionChains
8from selenium.webdriver.common.alert import Alert
9from selenium.webdriver.support.select import Select
12open_schema = yaml.safe_load("""
13oneOf:
14 - type: string
15 - type: object
16 properties:
17 url: {type: string}
18 query: {type: object}
19 required: [url]
20""")
23def Base_open(self, param):
24 """
25 - name: open google
26 open: https://www.google.com
27 - name: open google
28 open:
29 url: https://www.google.com/search
30 query:
31 q: keyword1
32 """
33 self.log.debug("open %s", param)
34 if isinstance(param, str):
35 self.driver.get(param)
36 elif isinstance(param, dict):
37 url = param.get("url", None)
38 if url is None:
39 raise Exception("cannot find open.url: %s" % (param))
40 query = param.get("query", {})
41 qstr = urllib.parse.urlencode(query)
42 if qstr != "":
43 url += "?"
44 url += qstr
45 self.driver.get(url)
46 return self.driver.current_url
49screenshot_schema = yaml.safe_load("""
50oneOf:
51 - type: string
52 - allOf:
53 - type: object
54 properties:
55 output: {type: string}
56 optimize: {type: boolean}
57 archive: {type: string}
58 crop:
59 oneOf:
60 - type: string
61 enum: [auto]
62 - type: array
63 items: {type: integer}
64 resize:
65 type: array
66 items: {type: integer}
67 - "$ref": "#/definitions/common/locator"
68""")
71def Base_screenshot(self, param):
72 """
73 - name: take screenshot 1
74 screenshot: shot1.png
75 - name: take screenshot 2
76 screenshot:
77 output: shot2.png
78 optimize: true
79 archive: images.tar
80 crop: auto
81 resize: [800, 600]
82 """
83 self.log.debug("screenshot %s", param)
84 if isinstance(param, str):
85 self.saveshot(param)
86 return param
87 elif isinstance(param, dict):
88 output = param.get("output")
89 if output is None:
90 # generate filename
91 ts = time.time()
92 msec = math.modf(ts)[0] * 1000
93 output = param.get("prefix", "")
94 output += time.strftime("%Y%m%d_%H%M%S", time.localtime(ts))
95 output += "_%03d.png" % (msec)
96 self.log.debug("filename generated %s", output)
97 self.saveshot(output)
98 elem = self.findmany2one(param)
99 if elem is not None:
100 x1 = elem.location['x']
101 y1 = elem.location['y']
102 x2 = x1 + elem.size['width']
103 y2 = y1 + elem.size['height']
104 nparam = {
105 "input": output,
106 "size": [x1, y1, x2, y2],
107 }
108 self.do_image_crop(nparam)
109 if param.get("crop", None) is not None:
110 nparam = {
111 "input": output,
112 "size": param.get("crop"),
113 }
114 self.do_image_crop(nparam)
115 if param.get("resize", None) is not None:
116 nparam = {
117 "input": output,
118 "size": param.get("resize"),
119 }
120 self.do_image_resize(nparam)
121 if param.get("optimize", False):
122 nparam = {
123 "input": output,
124 }
125 self.do_image_optimize(nparam)
126 if param.get("archive", False):
127 nparam = {
128 "output": param.get("archive"),
129 "input": output,
130 "delete": True,
131 }
132 self.do_image_archive(nparam)
133 return output
136click_schema = {"$ref": "#/definitions/common/locator"}
139def Base_click(self, param):
140 """
141 - name: click1
142 click:
143 id: elementid1
144 - name: click2
145 click:
146 xpath: //div[1]
147 """
148 self.findmany2one(param).click()
151submit_schema = click_schema
154def Base_submit(self, param):
155 """
156 - name: submit element
157 submit:
158 id: elementid1
159 """
160 self.findmany2one(param).submit()
163def Base_waitfor(self, param):
164 waiter = WebDriverWait(self.driver, param.get("timeout", 10))
165 simple_fn = [
166 "title_is", "title_contains", "url_changes", "url_contains", "url_matches",
167 "url_to_be", "number_of_windows_to_be",
168 ]
169 if "alert_is_present" in param:
170 return waiter.until(expected_conditions.alert_is_present())
171 for f in simple_fn:
172 if f in param:
173 return waiter.until(getattr(expected_conditions, f)(param.get(f)))
174 locator_fn = [
175 "element_located_to_be_selected", "element_to_be_clickable",
176 "frame_to_be_available_and_switch_to_it",
177 "invisibility_of_element_located", "presence_of_all_elements_located",
178 "presence_of_element_located", "visibility_of_all_elements_located",
179 "visibility_of_any_elements_located", "visibility_of_element_located"
180 ]
181 for f in locator_fn:
182 if f in param:
183 loc = self.getlocator(param)
184 if len(loc) != 2 or loc[0] is None:
185 raise Exception("locator not set: %s" % (param))
186 return waiter.until(getattr(expected_conditions, f)(loc))
187 locator_and_fn = {
188 "text_to_be_present_in_element": None,
189 "text_to_be_present_in_element_value": None,
190 "element_located_selection_state_to_be": "selected",
191 }
192 for f, v in locator_and_fn.items():
193 if f in param:
194 if v is None:
195 arg = self.getvalue(param)
196 else:
197 arg = param.get(v)
198 if arg is None:
199 raise Exception("missing argument %s: param=%s" % (v, param))
200 loc = self.getlocator(param)
201 if len(loc) != 2 or loc[0] is None:
202 raise Exception("locator not set: %s" % (param))
203 return waiter.until(getattr(expected_conditions, f)(loc, arg))
204 # other conditions:
205 # element_selection_state_to_be, element_to_be_selected,
206 # new_window_is_opened, staleness_of, visibility_of
207 raise Exception("not implemented: param=%s" % (param))
210script_schema = yaml.safe_load("""
211oneOf:
212 - type: string
213 - type: array
214 items: {type: string}
215 - type: object
216 properties:
217 file: {type: string}
218 required: [file]
219""")
222def Base_script(self, param):
223 """
224 - name: execute js
225 script: 'alert("hello")'
226 """
227 if isinstance(param, (list, tuple)):
228 for s in param:
229 self.driver.execute_script(s)
230 elif isinstance(param, str):
231 self.driver.execute_script(param)
232 elif isinstance(param, dict):
233 fname = param.get("file", None)
234 if fname is None:
235 raise Exception("no file")
236 with open(fname) as f:
237 self.driver.execute_script(f.read())
238 else:
239 raise Exception("parameter error: %s" % (param))
242history_schema = yaml.safe_load("""
243oneOf:
244 - type: string
245 enum: [forward, fwd, f, backward, back, b]
246 - type: array
247 items:
248 type: string
249 enum: [forward, fwd, f, backward, back, b]
250""")
253def Base_history(self, param):
254 """
255 - name: back
256 history: [back, fwd, back, refresh]
257 """
258 fwd = ("forward", "fwd", "f")
259 back = ("backward", "back", "b")
260 refresh = ("refresh", "reload", "r")
261 if isinstance(param, (tuple, list)):
262 for d in param:
263 if d in fwd:
264 self.driver.forward()
265 elif d in back:
266 self.driver.back()
267 elif d in refresh:
268 self.driver.refresh()
269 else:
270 raise Exception("no such direction: %s" % (d))
271 elif isinstance(param, str):
272 if param in fwd:
273 self.driver.forward()
274 elif param in back:
275 self.driver.back()
276 elif param in refresh:
277 self.driver.refresh()
278 else:
279 raise Exception("no such direction: %s" % (param))
280 else:
281 raise Exception("history: not supported direction: %s" % (param))
284sendKeys_schema = yaml.safe_load("""
285allOf:
286 - "$ref": "#/definitions/common/locator"
287 - "$ref": "#/definitions/common/textvalue"
288 - type: object
289 properties:
290 clear: {type: boolean}
291""")
294def Base_sendKeys(self, param):
295 """
296 - name: input username
297 sendKeys:
298 text: user1
299 id: elementid1
300 - name: input password
301 sendKeys:
302 password: site/password
303 # get text from $(pass site/password)
304 id: elementid2
305 """
306 clear = param.get("clear", False)
307 txt = self.getvalue(param)
308 if txt is None:
309 raise Exception("text not set: param=%s" % (param))
310 elem = self.findmany2one(param)
311 if clear:
312 elem.clear()
313 elem.send_keys(txt)
314 return self.return_element(param, elem)
317setTextValue_schema = yaml.safe_load("""
318allOf:
319 - "$ref": "#/definitions/common/locator"
320 - "$ref": "#/definitions/common/textvalue"
321""")
324def Base_setTextValue(self, param):
325 """
326 - name: input username
327 setTextValue:
328 text: |
329 multi line text1
330 multi line text2
331 id: elementid1
332 """
333 txt = self.getvalue(param)
334 if txt is None:
335 raise Exception("text not set: param=%s" % (param))
336 elem = self.findmany2one(param)
337 self.driver.execute_script("arguments[0].value = arguments[1];", elem, txt)
338 return self.return_element(param, elem)
341save_schema = yaml.safe_load("""
342allOf:
343 - type: object
344 properties:
345 mode:
346 type: string
347 enum: ["source", "source_outer", "text", "title"]
348 output: {type: string}
349 - "$ref": "#/definitions/common/locator"
350""")
353def Base_save(self, param):
354 """
355 - name: save page title
356 save:
357 mode: title
358 output: title.txt
359 - name: save page content
360 save:
361 mode: source
362 id: element1
363 - name: copy page content to variable
364 save:
365 mode: text
366 id: element2
367 register: title1
368 """
369 mode = param.get("mode", "source")
370 locator = self.getlocator(param)
371 if mode == "source":
372 if locator[0] is None:
373 txt = [self.driver.page_source]
374 else:
375 txt = []
376 for p in self.findmany(param):
377 txt.append(p.get_attribute("innerHTML"))
378 elif mode == "source_outer":
379 if locator[0] is None:
380 txt = [self.driver.page_source]
381 else:
382 txt = []
383 for p in self.findmany(param):
384 txt.append(p.get_attribute("outerHTML"))
385 elif mode == "title":
386 txt = [self.driver.title]
387 elif mode == "text":
388 if locator[0] is None:
389 txt = [self.driver.find_element_by_xpath("/html").text]
390 else:
391 txt = []
392 for p in self.findmany(param):
393 txt.append(p.text)
394 output = param.get("output", None)
395 if output is not None:
396 with open(output, "w") as f:
397 f.write("\n".join(txt))
398 return txt
401dragdrop_schema = yaml.safe_load("""
402type: object
403properties:
404 src: {"$ref": "#/definitions/common/locator"}
405 dst: {"$ref": "#/definitions/common/locator"}
406""")
409def Base_dragdrop(self, param):
410 """
411 - name: drag and drop
412 dragdrop:
413 src:
414 xpath: //div[1]
415 dst:
416 select: "$.x.y.z"
417 """
418 src = self.findmany2one(param.get("src"))
419 dst = self.findmany2one(param.get("dst"))
420 ActionChains(self.driver).drag_and_drop(src, dst).perform()
423switch_schema = yaml.safe_load("""
424oneOf:
425 - type: string
426 enum: [default]
427 - type: boolean
428 - type: "null"
429 - type: object
430 properties:
431 window: {type: string}
432 frame: {type: string}
433""")
436def Base_switch(self, param):
437 """
438 - name: switch window
439 switch:
440 window: win1
441 - name: switch frame
442 switch:
443 frame: frm1
444 - name: switch to default window
445 switch: default
446 """
447 if param in ("default", None, {}, True):
448 self.driver.switch_to_default_content()
449 elif "window" in param:
450 self.driver.switch_to_window(param.get("window"))
451 elif "frame" in param:
452 self.driver.switch_to_frame(param.get("frame"))
455def Base_dropfile(self, param):
456 """
457 - name: drop file
458 dropfile:
459 filename: /path/to/file
460 id: element1
461 """
462 raise Exception("not implemented yet")
465deletecookie_schema = yaml.safe_load("""
466oneOf:
467 - type: array
468 items: {type: string}
469 - type: string
470""")
473def Base_deletecookie(self, param):
474 """
475 - name: delete all cookie
476 deletecookie: all
477 - name: clear cookie a, b, c
478 deletecookie: [a, b, c]
479 """
480 if param == "all":
481 self.driver.delete_all_cookies()
482 elif isinstance(param, (tuple, list)):
483 for c in param:
484 self.driver.delete_cookie(c)
485 elif isinstance(param, str):
486 self.driver.delete_cookie(param)
487 else:
488 raise Exception("invalid argument")
491alertOK_schema = {"type": "boolean"}
494def Base_alertOK(self, param):
495 """
496 - name: accept alert
497 alertOK: true
498 - name: cancel alert
499 alertOK: false
500 """
501 if isinstance(param, bool):
502 if param:
503 Alert(self.driver).accept()
504 else:
505 Alert(self.driver).dismiss()
508auth_schema = yaml.safe_load("""
509type: object
510properties:
511 username: {type: string}
512 password: {type: string}
513""")
516def Base_auth(self, param):
517 """
518 - name: basic/digest auth
519 auth:
520 username: user1
521 password: password1
522 """
523 user = param.get("username", "")
524 passwd = param.get("password", "")
525 Alert(self.driver).authenticate(user, passwd)
528select_schema = yaml.safe_load("""
529allOf:
530 - "$ref": "#/definitions/common/locator"
531 - type: object
532 properties:
533 by_index: {type: integer}
534 by_value: {type: string}
535 by_text: {type: string}
536 all: {type: boolean}
537 return:
538 type: string
539 enum: [selected, first, all]
540""")
543def Base_select(self, param):
544 """
545 - name: select 1st
546 select:
547 id: element1
548 by_index: 1
549 - name: select by value
550 select:
551 id: element1
552 by_value: value1
553 - name: select by visible text
554 select:
555 id: element1
556 by_text: "text 1"
557 """
558 elem = self.findone(param)
559 if elem is None:
560 raise Exception("element not found: %s" % (param))
561 flag = param.get("deselect", False)
562 sel = Select(elem)
563 if "by_index" in param:
564 if flag:
565 sel.deselect_by_index(param.get("by_index"))
566 else:
567 sel.select_by_index(param.get("by_index"))
568 elif "by_value" in param:
569 if flag:
570 sel.deselect_by_value(param.get("by_value"))
571 else:
572 sel.select_by_value(param.get("by_value"))
573 elif "by_text" in param:
574 if flag:
575 sel.deselect_by_visible_text(param.get("by_text"))
576 else:
577 sel.select_by_visible_text(param.get("by_text"))
578 elif param.get("all", False):
579 if flag:
580 sel.deselect_all()
581 else:
582 sel.select_all()
583 retp = param.get("return", "selected")
584 if retp == "selected":
585 res = sel.all_selected_options
586 elif retp == "first":
587 res = sel.first_selected_option
588 elif retp == "all":
589 res = sel.options
590 else:
591 return
592 return self.return_element(param, res)
595scroll_schema = yaml.safe_load("""
596anyOf:
597 - "$ref": "#/definitions/common/locator"
598 - type: object
599 properties:
600 relative:
601 type: array
602 items: {type: integer}
603 absolute:
604 type: array
605 items: {type: integer}
606 percent:
607 type: array
608 items: {type: number}
609 position:
610 type: string
611 enum: [top, bottom, right, left, topright, topleft, bottomright, bottomleft]
612""")
615def scrollto(fn, x, y):
616 return "window.%s(%s,%s)" % (fn, x, y)
619def Base_scroll(self, param):
620 """
621 - name: scroll down 100 pixel
622 scroll:
623 relative: [0, 100]
624 - name: scroll to pixel
625 scroll:
626 absolute: [0, 100]
627 - name: scroll to percent
628 scroll:
629 percent: [0, 50]
630 - name: scroll to position
631 scroll:
632 position: bottom
633 - name: scroll to element
634 scroll:
635 id: element1
636 """
637 xmax, ymax = "document.body.scrollWidth", "document.body.scrollHeight"
639 relative = param.get("relative")
640 if relative is not None and isinstance(relative, (tuple, list)) and len(relative) == 2:
641 self.driver.execute_script(scrollto("scrollBy", relative[0], relative[1]))
642 absolute = param.get("absolute")
643 if absolute is not None and isinstance(absolute, (tuple, list)) and len(absolute) == 2:
644 self.driver.execute_script(scrollto("scrollTo", absolute[0], absolute[1]))
645 percent = param.get("percent")
646 if percent is not None and isinstance(percent, (tuple, list)) and len(percent) == 2:
647 self.driver.execute_script(scrollto("scrollTo",
648 "%s*%f" % (xmax, percent[0] / 100.0),
649 "%s*%f" % (ymax, percent[1] / 100.0)))
650 pos = param.get("position")
651 if pos in ("bottom", "bottomleft"):
652 self.driver.execute_script(scrollto("scrollTo", 0, ymax))
653 elif pos in ("right", "topright"):
654 self.driver.execute_script(scrollto("scrollTo", xmax, 0))
655 elif pos in ("bottomright",):
656 self.driver.execute_script(scrollto("scrollTo", xmax, ymax))
657 elif pos in ("top", "topleft", "left"):
658 self.driver.execute_script(scrollto("scrollTo", 0, 0))
659 locator = self.getlocator(param)
660 if locator[0] is not None:
661 elem = self.findmany2one(param)
662 if elem is not None:
663 self.driver.execute_script("arguments[0].scrollIntoView();", elem)
666def Base_shutdown(self, params):
667 """
668 - name: shutdown webdriver
669 shutdown: null
670 """
671 self.shutdown_driver()
674def Base_browser_setting(self, params):
675 restart = params.pop("restart", False)
676 copt = params.pop("options", {})
677 self.browser_args.update(params)
678 if len(copt) != 0:
679 opt = self.get_options()
680 for k, v in copt.items():
681 if hasattr(opt, k) and callable(getattr(opt, k)):
682 getattr(opt, k)(v)
683 else:
684 self.log.error("no such option: %s(%s): %s", k, v, dir(opt))
685 raise Exception("no such option: %s" % (k))
686 self.browser_args["options"] = opt
687 if restart:
688 self.do_shutdown({})