screenshot_utils.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. """
  2. All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  3. its licensors.
  4. For complete copyright and license terms please see the LICENSE at the root of this
  5. distribution (the "License"). All use of this software is governed by the License,
  6. or, if provided, by the license below or the license accompanying this file. Do not
  7. remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. """
  10. import os
  11. import string
  12. from .file_utils import move_file
  13. from ly_test_tools.environment.waiter import wait_for
  14. from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
  15. from ly_remote_console.remote_console_commands import capture_screenshot_command as capture_screenshot_command
  16. from ly_remote_console.remote_console_commands import send_command_and_expect_response as send_command_and_expect_response
  17. def get_next_screenshot_at_path(screenshot_path, prefix='screenshot', num_digits=4):
  18. """
  19. :param screenshot_path: Root folder where the screenshots are being generated by the Launcher pr Editor.
  20. :param prefix: Generated screenshot files are named sequentially using the prefix.
  21. e.g: screenshot0000.jpg, screenshot0001.jpg and so on.
  22. :param num_digits: How many digits are used for file name formation.
  23. :return: A string with the file name (relative to screenshot_path).
  24. """
  25. max_counter = 10**num_digits
  26. counter = 0
  27. while counter < max_counter:
  28. numberstr = "{}".format(counter)
  29. formattednumber = numberstr.zfill(num_digits)
  30. filename = "{}{}.jpg".format(prefix, formattednumber)
  31. filepath = os.path.join(screenshot_path, filename)
  32. if not os.path.exists(filepath):
  33. #This filename is available.
  34. return filename
  35. raise AssertionError("All possible screenshot names at directory {} are taken".format(screenshot_path))
  36. def take_screenshot(remote_console_instance, workspace, screenshot_name):
  37. """
  38. Takes an in game screenshot using the remote console instance passed in, validates that the screenshot exists
  39. and then renames that screenshot to something defined by the user of this function.
  40. :param remote_console_instance: Remote console instance that is attached to a specific launcher instance
  41. :param workspace: workspace instance so we can get the platform cache folder.
  42. :param screenshot_name: Name of the screenshot
  43. :return: None
  44. """
  45. screenshot_path = os.path.join(workspace.paths.platform_cache(), 'user', 'screenshots')
  46. expected_screenshot_name = get_next_screenshot_at_path(screenshot_path)
  47. capture_screenshot_command(remote_console_instance)
  48. wait_for(lambda: os.path.exists(os.path.join(screenshot_path, expected_screenshot_name)),
  49. timeout=10,
  50. exc=AssertionError('Screenshot at path:{} and with name:{} not found.'.format(screenshot_path, expected_screenshot_name)) )
  51. wait_for(lambda: rename_screenshot(screenshot_path, screenshot_name),
  52. timeout=10,
  53. exc=AssertionError('Screenshot at path:{} and with name:{} is still in use.'.format(screenshot_path, screenshot_name)))
  54. def rename_screenshot(screenshot_path, screenshot_name):
  55. """
  56. Tries to rename the screenshot when the file is done being written to
  57. :param screenshot_path: Path to the Screenshot folder
  58. :param screenshot_name: Name we wish to change the screenshot to
  59. :return: True when operation is completed, False if the file is still in use
  60. """
  61. try:
  62. src_img = os.path.join(screenshot_path, 'screenshot0000.jpg')
  63. dst_img = os.path.join(screenshot_path, '{}.jpg'.format(screenshot_name))
  64. print('Trying to rename {} to {}'.format(src_img, dst_img))
  65. os.rename(src_img, dst_img)
  66. return True
  67. except Exception as e:
  68. print('Found error {0} when trying to rename screenshot.'.format(str(e)))
  69. return False
  70. def move_screenshots(screenshot_path, file_type, logs_path):
  71. """
  72. Moves screenshots of a specific file type to the flume location so we can gather all of the screenshots we took.
  73. :param screenshot_path: Path to the screenshot folder
  74. :param file_type: Types of Files to look for. IE .jpg, .tif, etc
  75. :param logs_path: Path where flume gathers logs to be upload
  76. """
  77. for file_name in os.listdir(screenshot_path):
  78. if file_name.endswith(file_type):
  79. move_file(screenshot_path, logs_path, file_name)
  80. def move_screenshots_to_artifacts(screenshot_path, file_type, artifact_manager):
  81. """
  82. Saves screenshots of a specific file type to the artifact manager then removes the original files
  83. :param screenshot_path: Path to the screenshot folder
  84. :param file_type: Types of Files to look for. IE .jpg, .tif, etc
  85. :param artifact_manager: The artifact manager to save the artifacts to
  86. """
  87. for file_name in os.listdir(screenshot_path):
  88. if file_name.endswith(file_type):
  89. full_path_name = os.path.join(screenshot_path, file_name)
  90. artifact_manager.save_artifact(full_path_name)
  91. os.remove(full_path_name)
  92. def compare_golden_image(similarity_threshold, screenshot, screenshot_path, golden_image_name,
  93. golden_image_path=None):
  94. """
  95. This function assumes that your golden image filename contains the same base screenshot name and the word "golden"
  96. ex. pc_gamelobby_golden
  97. :param similarity_threshold: A float from 0.0 - 1.0 that determines how similar images must be or an asserts
  98. :param screenshot: A string that is the full name of the screenshot (ex. 'gamelobby_host.jpg')
  99. :param screenshot_path: A string that contains the path to the screenshots
  100. :param golden_image_path: A string that contains the path to the golden images, defaults to the screenshot_path
  101. :return:
  102. """
  103. if golden_image_path is None:
  104. golden_image_path = screenshot_path
  105. mean_similarity = compare_screenshots((os.path.join(screenshot_path, screenshot)),
  106. (os.path.join(golden_image_path, golden_image_name)))
  107. assert mean_similarity > similarity_threshold, \
  108. '{} screenshot comparison failed! Mean similarity value is: {}'\
  109. .format(screenshot, mean_similarity)
  110. def download_qa_golden_images(project_name, destination_dir, platform):
  111. """
  112. Downloads the golden images for a specified project from s3. The project_name, platform, and filetype are used to
  113. filter which images will be downloaded as the golden images.
  114. https://s3.console.aws.amazon.com/s3/buckets/ly-qae-jenkins-configs/golden-images/?region=us-west-1&tab=overview
  115. :param project_name: a string of the project name of the folder in s3. ex: 'MultiplayerSample'
  116. :param destination_dir: a string of where the images will be downloaded to
  117. :param platform: a string for the platform type ('pc', 'android', 'ios', 'darwin', 'provo')
  118. :param filetype: a string for the file type. ex: '.jpg', '.png'
  119. :return:
  120. """
  121. # Currently we import s3_utils here instead of at the top because this is the only method that needs it,
  122. # and s3_utils has an unmet dependency on boto3 that hasn't been resolved. Once s3_utils is functional again,
  123. # this can move back to the top of the file.
  124. try:
  125. from . import s3_utils as s3_utils
  126. except ImportError:
  127. raise Exception("Failed to import s3_utils")
  128. # end s3_utils import
  129. bucket_name = 'ly-qae-jenkins-configs'
  130. path = 'golden-images/{}/{}/'.format(project_name, platform)
  131. if not s3_utils.key_exists_in_bucket(bucket_name, path):
  132. raise s3_utils.KeyDoesNotExistError("Key '{}' does not exist in S3 bucket {}".format(path, bucket_name))
  133. for image in s3_utils.s3.Bucket(bucket_name).objects.filter(Prefix=path):
  134. file_name = string.replace(image.key, path, '')
  135. if file_name != '':
  136. s3_utils.download_from_bucket(bucket_name, image.key, destination_dir, file_name)
  137. def _retry_command(remote_console_instance, command, output, tries=10, timeout=10):
  138. """
  139. Retries specified console command multiple times and asserts if it still can not send.
  140. :param remote_console: the remote console connected to the launcher.
  141. :param command: the command to send to the console.
  142. :param output: The expected output to check if the command was sent successfully.
  143. :param tries: The amount of times to try before asserting.
  144. :param timeout: The amount of time in seconds to wait for each retry send.
  145. :return: True if succeeded, will assert otherwise.
  146. """
  147. while tries > 0:
  148. tries -= 1
  149. try:
  150. send_command_and_expect_response(remote_console_instance, command, output)
  151. return True
  152. except:
  153. pass #Do nothing. Let the number of tries get to 0 if necessary.
  154. assert False, "Command \"{}\" failed to run in remote console.".format(command)
  155. def prepare_for_screenshot_compare(remote_console_instance):
  156. """
  157. Prepares launcher for screenshot comparison. Removes any debug text and antialiasing that may result in interference
  158. with the comparison.
  159. :param remote_console_instance: Remote console instance that is attached to a specific launcher instance
  160. :return:
  161. """
  162. wait_for(lambda: _retry_command(remote_console_instance, 'r_displayinfo 0',
  163. '$3r_DisplayInfo = $60 $5[DUMPTODISK, RESTRICTEDMODE]$4'))
  164. wait_for(lambda: _retry_command(remote_console_instance, 'r_antialiasingmode 0',
  165. '$3r_AntialiasingMode = $60 $5[]$4'))
  166. wait_for(lambda: _retry_command(remote_console_instance, 'e_WaterOcean 0',
  167. '$3e_WaterOcean = $60 $5[]$4'))