user.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import threading
  2. import time
  3. import pytest
  4. from nicegui import globals, ui
  5. from selenium import webdriver
  6. from selenium.common.exceptions import NoSuchElementException
  7. from selenium.webdriver.remote.webelement import WebElement
  8. PORT = 3392
  9. IGNORED_CLASSES = ['row', 'column', 'q-card', 'q-field', 'q-field__label', 'q-input']
  10. class User():
  11. def __init__(self, selenium: webdriver.Chrome) -> None:
  12. self.selenium = selenium
  13. self.server_thread = None
  14. def start_server(self) -> None:
  15. '''Start the webserver in a separate thread. This is the equivalent of `ui.run()` in a normal script.'''
  16. self.server_thread = threading.Thread(target=ui.run, kwargs={'port': PORT, 'show': False, 'reload': False})
  17. self.server_thread.start()
  18. def stop_server(self) -> None:
  19. '''Stop the webserver.'''
  20. self.selenium.close()
  21. globals.server.should_exit = True
  22. self.server_thread.join()
  23. def open(self, path: str) -> None:
  24. if self.server_thread is None:
  25. self.start_server()
  26. start = time.time()
  27. while True:
  28. try:
  29. self.selenium.get(f'http://localhost:{PORT}{path}')
  30. break
  31. except Exception:
  32. if time.time() - start > 3:
  33. raise
  34. time.sleep(0.1)
  35. if not self.server_thread.is_alive():
  36. raise RuntimeError('The NiceGUI server has stopped running')
  37. def should_see(self, text: str) -> None:
  38. assert self.selenium.title == text or self.find(text), \
  39. f'could not find "{text}" on:\n{self.page()}'
  40. def should_not_see(self, text: str) -> None:
  41. assert self.selenium.title != text
  42. with pytest.raises(AssertionError):
  43. element = self.find(text)
  44. print(element.get_attribute('outerHTML'))
  45. def click(self, target_text: str) -> None:
  46. self.find(target_text).click()
  47. def find(self, text: str) -> WebElement:
  48. try:
  49. return self.selenium.find_element_by_xpath(f'//*[contains(text(),"{text}")]')
  50. except NoSuchElementException:
  51. raise AssertionError(f'Could not find "{text}" on:\n{self.page()}')
  52. def page(self, with_extras: bool = False) -> str:
  53. return f'Title: {self.selenium.title}\n\n' + \
  54. self.content(self.selenium.find_element_by_tag_name('body'), with_extras=with_extras)
  55. def content(self, element: WebElement, indent: str = '', with_extras: bool = False) -> str:
  56. content = ''
  57. classes: list[str] = []
  58. for child in element.find_elements_by_xpath('./*'):
  59. is_element = False
  60. is_group = False
  61. render_children = True
  62. assert isinstance(child, WebElement)
  63. if not child.find_elements_by_xpath('./*') and child.text:
  64. is_element = True
  65. content += f'{indent}{child.text}'
  66. classes = child.get_attribute('class').strip().split()
  67. if classes:
  68. if classes[0] in ['row', 'column', 'q-card']:
  69. content += classes[0].removeprefix('q-')
  70. is_element = True
  71. is_group = True
  72. if classes[0] == 'q-field':
  73. try:
  74. name = child.find_element_by_class_name('q-field__label').text
  75. except NoSuchElementException:
  76. name = ''
  77. input = child.find_element_by_tag_name('input')
  78. value = input.get_attribute('value') or input.get_attribute('placeholder')
  79. content += f'{indent}{name}: {value}'
  80. render_children = False
  81. is_element = True
  82. [classes.remove(c) for c in IGNORED_CLASSES if c in classes]
  83. for i, c in enumerate(classes):
  84. classes[i] = c.removeprefix('q-field--')
  85. if is_element and with_extras:
  86. content += f' [class: {" ".join(classes)}]'
  87. if is_element:
  88. content += '\n'
  89. if render_children:
  90. content += self.content(child, indent + (' ' if is_group else ''), with_extras)
  91. return content
  92. def get_tags(self, name: str) -> list[WebElement]:
  93. return self.selenium.find_elements_by_tag_name(name)
  94. def get_attributes(self, tag: str, attribute: str) -> list[str]:
  95. return [t.get_attribute(attribute) for t in self.get_tags(tag)]
  96. def sleep(self, t: float) -> None:
  97. time.sleep(t)