Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

run_tests.py 9.8 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #!/usr/bin/env python3
  2. """
  3. Selenium-based test suite for Pannellum
  4. Dependencies:
  5. Python 3, Selenium Python bindings, Pillow, NumPy
  6. Either: Firefox & geckodriver or Chrome & chromedriver
  7. Run tests for Pannellum, set up with Continuous Integration.
  8. Contributed by Vanessa Sochat, JOSS Review 2019.
  9. See the project repository for licensing information.
  10. """
  11. from random import choice
  12. from threading import Thread
  13. from http.server import SimpleHTTPRequestHandler
  14. from socketserver import TCPServer
  15. import argparse
  16. import io
  17. import os
  18. import re
  19. import subprocess
  20. import sys
  21. import time
  22. import numpy as np
  23. from PIL import Image, ImageChops
  24. from selenium.common.exceptions import TimeoutException
  25. from selenium import webdriver
  26. from selenium.webdriver.common.action_chains import ActionChains
  27. class PannellumServer(SimpleHTTPRequestHandler):
  28. """Here we subclass SimpleHTTPServer to capture error messages.
  29. """
  30. def log_message(self, format, *args):
  31. """
  32. Log to standard error with a date time string,
  33. and then call any subclass specific logging functions.
  34. """
  35. sys.stderr.write(
  36. "%s - - [%s] %s\n"
  37. % (self.address_string(), self.log_date_time_string(), format % args)
  38. )
  39. # Workaround for error trying to GET html
  40. if not re.search("div", format % args) and not re.search(
  41. "function", format % args
  42. ):
  43. if re.search("404", format % args):
  44. raise IOError(format % args)
  45. def log_error(self, format, *args):
  46. """Catch errors in the log_messages instead.
  47. """
  48. pass
  49. class PannellumTester(object):
  50. """Bring up a server with a testing robot.
  51. """
  52. def __init__(self, port=None, browser="Chrome", headless=False):
  53. self.handler = PannellumServer
  54. if port:
  55. self.port = port
  56. else:
  57. self.port = choice(range(8000, 9999))
  58. print("Selected port is %s" % self.port)
  59. self.httpd = TCPServer(("", self.port), self.handler)
  60. self.server = Thread(target=self.httpd.serve_forever)
  61. self.server.setDaemon(True)
  62. self.server.start()
  63. self.started = True
  64. self.pause_time = 100
  65. self.browser = None
  66. self.headless = headless
  67. self.display = None
  68. self.driver = browser
  69. def take_screenshot(self, element_id, filename=None):
  70. """Take a screenshot of an element with a given ID.
  71. """
  72. element = self.browser.find_element_by_id(element_id)
  73. img = Image.open(io.BytesIO(element.screenshot_as_png)).convert("RGB")
  74. if filename is not None:
  75. img.save(filename)
  76. return img
  77. def equal_images(self, reference, comparator, name, threshold=5):
  78. """Compare two images, both loaded with PIL, based on pixel differences."""
  79. diff = np.mean(np.array(ImageChops.difference(reference, comparator)))
  80. print("%s difference: %s" % (name, diff))
  81. if diff >= threshold:
  82. comparator.save("tests/" + name + "-comparison.png")
  83. raise ValueError("Screenshot difference is above threshold!")
  84. def run_tests(self, create_ref=False):
  85. """Run tests for Pannellum."""
  86. print("Loading page...")
  87. self.get_page("http://localhost:%s/tests/tests.html" % self.port)
  88. print("Running tests...")
  89. time.sleep(5)
  90. assert self.browser.execute_script("return viewer.isLoaded()") is True
  91. # Check equirectangular
  92. assert self.browser.execute_script(
  93. "return viewer.getScene() == 'equirectangular'"
  94. )
  95. if create_ref:
  96. self.take_screenshot("panorama", "tests/equirectangular.png")
  97. subprocess.call(
  98. ["optipng", "-o7", "-strip", "all", "tests/equirectangular.png"]
  99. )
  100. else:
  101. reference = Image.open("tests/equirectangular.png")
  102. comparator = self.take_screenshot("panorama")
  103. self.equal_images(reference, comparator, "equirectangular")
  104. print("PASS: equirectangular")
  105. # Check movement
  106. self.browser.execute_script("viewer.setPitch(30).setYaw(-20).setHfov(90)")
  107. time.sleep(2)
  108. assert self.browser.execute_script(
  109. "return viewer.getPitch() == 30 && viewer.getYaw() == -20 && viewer.getHfov() == 90"
  110. )
  111. self.browser.find_element_by_class_name("pnlm-zoom-in").click()
  112. time.sleep(1)
  113. assert self.browser.execute_script("return viewer.getHfov() == 85")
  114. self.browser.find_element_by_class_name("pnlm-zoom-out").click()
  115. time.sleep(1)
  116. assert self.browser.execute_script("return viewer.getHfov() == 90")
  117. print("PASS: movement")
  118. # Check look at
  119. self.browser.execute_script("viewer.lookAt(-10, 90, 100)")
  120. time.sleep(2)
  121. assert self.browser.execute_script(
  122. "return viewer.getPitch() == -10 && viewer.getYaw() == 90 && viewer.getHfov() == 100"
  123. )
  124. print("PASS: look at")
  125. # Check cube
  126. self.browser.execute_script("viewer.loadScene('cube')")
  127. time.sleep(5)
  128. assert self.browser.execute_script("return viewer.getScene() == 'cube'")
  129. if create_ref:
  130. self.take_screenshot("panorama", "tests/cube.png")
  131. subprocess.call(["optipng", "-o7", "-strip", "all", "tests/cube.png"])
  132. else:
  133. reference = Image.open("tests/cube.png")
  134. comparator = self.take_screenshot("panorama")
  135. self.equal_images(reference, comparator, "cube")
  136. # Check hot spot
  137. self.browser.find_element_by_class_name("pnlm-scene").click()
  138. time.sleep(5)
  139. assert self.browser.execute_script("return viewer.getScene() == 'multires'")
  140. print("PASS: hot spot")
  141. # Check multires
  142. if create_ref:
  143. self.take_screenshot("panorama", "tests/multires.png")
  144. subprocess.call(["optipng", "-o7", "-strip", "all", "tests/multires.png"])
  145. else:
  146. reference = Image.open("tests/multires.png")
  147. comparator = self.take_screenshot("panorama")
  148. self.equal_images(reference, comparator, "multires")
  149. # Check hotspot dragging - move from (20, 20) to (0, 0)
  150. action = ActionChains(self.browser)
  151. action.drag_and_drop(
  152. self.browser.find_element_by_class_name("pnlm-hotspot"),
  153. self.browser.find_element_by_class_name(
  154. "pnlm-render-container"
  155. ), # drops in the middle of the element
  156. )
  157. action.perform()
  158. time.sleep(1)
  159. assert self.browser.execute_script(
  160. "var hs = viewer.getConfig().hotSpots[0]; return Math.abs(hs.yaw) < 0.001 && Math.abs(hs.pitch) < 0.001"
  161. )
  162. print("PASS: hot spot dragging")
  163. self.httpd.server_close()
  164. def get_browser(self, name=None):
  165. """Return a browser if it hasn't been initialized yet.
  166. """
  167. if name is None:
  168. name = self.driver
  169. log_path = "tests/%s-driver.log" % name.lower()
  170. if self.browser is None:
  171. if name.lower() == "firefox":
  172. fp = webdriver.FirefoxProfile()
  173. fp.set_preference("layout.css.devPixelsPerPx", "1.0")
  174. self.browser = webdriver.Firefox(
  175. service_log_path=log_path, firefox_profile=fp
  176. )
  177. self.browser.set_window_size(800, 600)
  178. else:
  179. options = webdriver.ChromeOptions()
  180. options.add_argument("headless")
  181. options.add_argument("no-sandbox")
  182. options.add_argument("window-size=800x600")
  183. self.browser = webdriver.Chrome(
  184. service_log_path=log_path, options=options
  185. )
  186. return self.browser
  187. def get_page(self, url):
  188. """Open a particular URL, checking for timeout.
  189. """
  190. if self.browser is None:
  191. self.browser = self.get_browser()
  192. try:
  193. return self.browser.get(url)
  194. except TimeoutException:
  195. print("Browser request timeout. Are you connected to the internet?")
  196. self.browser.close()
  197. sys.exit(1)
  198. def stop(self):
  199. """Close any running browser or server and shut down the robot.
  200. """
  201. if self.browser is not None:
  202. self.browser.close()
  203. self.httpd.server_close()
  204. if self.display is not None:
  205. self.display.close()
  206. def get_parser():
  207. parser = argparse.ArgumentParser(description="Run tests for Pannellum")
  208. parser.add_argument(
  209. "--port",
  210. "-p",
  211. dest="port",
  212. help="Port to run web server",
  213. type=int,
  214. default=None,
  215. )
  216. parser.add_argument(
  217. "--headless",
  218. dest="headless",
  219. help="Start a display before browser",
  220. action="store_true",
  221. default=False,
  222. )
  223. parser.add_argument(
  224. "--create-ref", dest="create_ref", action="store_true", default=False
  225. )
  226. parser.add_argument(
  227. "--browser",
  228. "-b",
  229. dest="browser",
  230. choices=["Firefox", "Chrome"],
  231. help="Browser driver to use for the robot",
  232. type=str,
  233. default="Chrome",
  234. )
  235. return parser
  236. def main():
  237. parser = get_parser()
  238. try:
  239. args = parser.parse_args()
  240. except:
  241. sys.exit(0)
  242. # Add this script's directory, in case it contains driver binaries
  243. here = os.path.abspath(os.path.dirname(__file__))
  244. os.environ["PATH"] = here + ":" + os.environ["PATH"]
  245. os.chdir(here)
  246. # We must be in root directory
  247. os.chdir("..")
  248. # Initialize the tester
  249. tester = PannellumTester(
  250. browser=args.browser, port=args.port, headless=args.headless
  251. )
  252. # Run tests
  253. tester.run_tests(create_ref=args.create_ref)
  254. # Clean up shop!
  255. tester.stop()
  256. if __name__ == "__main__":
  257. main()