user.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  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).text == text
  39. def should_not_see(self, text: str) -> None:
  40. assert self.selenium.title != text
  41. with pytest.raises(AssertionError):
  42. element = self.find(text)
  43. print(element.get_attribute('outerHTML'))
  44. def click(self, target_text: str) -> None:
  45. self.find(target_text).click()
  46. def find(self, text: str) -> WebElement:
  47. try:
  48. return self.selenium.find_element_by_xpath(f'//*[contains(text(),"{text}")]')
  49. except NoSuchElementException:
  50. raise AssertionError(f'Could not find "{text}" on:\n{self.page()}')
  51. def page(self, with_extras: bool = False) -> str:
  52. return f'Title: {self.selenium.title}\n\n' + \
  53. self.content(self.selenium.find_element_by_tag_name('body'), with_extras=with_extras)
  54. def content(self, element: WebElement, indent: str = '', with_extras: bool = False) -> str:
  55. content = ''
  56. classes: list[str] = []
  57. for child in element.find_elements_by_xpath('./*'):
  58. is_element = False
  59. is_group = False
  60. render_children = True
  61. assert isinstance(child, WebElement)
  62. if not child.find_elements_by_xpath('./*') and child.text:
  63. is_element = True
  64. content += f'{indent}{child.text}'
  65. classes = child.get_attribute('class').strip().split()
  66. if classes:
  67. if classes[0] in ['row', 'column', 'q-card']:
  68. content += classes[0].removeprefix('q-')
  69. is_element = True
  70. is_group = True
  71. if classes[0] == 'q-field':
  72. try:
  73. name = child.find_element_by_class_name('q-field__label').text
  74. except NoSuchElementException:
  75. name = ''
  76. input = child.find_element_by_tag_name('input')
  77. value = input.get_attribute('value') or input.get_attribute('placeholder')
  78. content += f'{indent}{name}: {value}'
  79. render_children = False
  80. is_element = True
  81. [classes.remove(c) for c in IGNORED_CLASSES if c in classes]
  82. for i, c in enumerate(classes):
  83. classes[i] = c.removeprefix('q-field--')
  84. if is_element and with_extras:
  85. content += f' [class: {" ".join(classes)}]'
  86. if is_element:
  87. content += '\n'
  88. if render_children:
  89. content += self.content(child, indent + (' ' if is_group else ''), with_extras)
  90. return content
  91. def get_tags(self, name: str) -> list[WebElement]:
  92. return self.selenium.find_elements_by_tag_name(name)
  93. def get_attributes(self, tag: str, attribute: str) -> list[str]:
  94. return [t.get_attribute(attribute) for t in self.get_tags(tag)]
  95. def sleep(self, t: float) -> None:
  96. time.sleep(t)