diff --git a/.github/scripts/build_assets/geckodriver-v0.29.1-win64/geckodriver.exe b/.github/scripts/build_assets/geckodriver-v0.29.1-win64/geckodriver.exe deleted file mode 100644 index a01047673..000000000 Binary files a/.github/scripts/build_assets/geckodriver-v0.29.1-win64/geckodriver.exe and /dev/null differ diff --git a/.github/scripts/build_assets/geckodriver-v0.29.1-win64/README.md b/.github/scripts/build_assets/geckodriver-v0.30.0-win64/README.md similarity index 93% rename from .github/scripts/build_assets/geckodriver-v0.29.1-win64/README.md rename to .github/scripts/build_assets/geckodriver-v0.30.0-win64/README.md index 008b11104..de90fe4ea 100644 --- a/.github/scripts/build_assets/geckodriver-v0.29.1-win64/README.md +++ b/.github/scripts/build_assets/geckodriver-v0.30.0-win64/README.md @@ -3,4 +3,4 @@ Proxy for using W3C WebDriver compatible clients to interact with Gecko-based br This program provides the HTTP API described by the WebDriver protocol to communicate with Gecko browsers, such as Firefox. It translates calls into the Marionette remote protocol by acting as a proxy between the local- and remote ends. -This application was taken from: https://github.com/mozilla/geckodriver/releases/tag/v0.29.1 +This application was taken from: https://github.com/mozilla/geckodriver/releases/tag/v0.30.0 diff --git a/.github/scripts/build_assets/geckodriver-v0.30.0-win64/geckodriver.exe b/.github/scripts/build_assets/geckodriver-v0.30.0-win64/geckodriver.exe new file mode 100644 index 000000000..c59764ab2 Binary files /dev/null and b/.github/scripts/build_assets/geckodriver-v0.30.0-win64/geckodriver.exe differ diff --git a/.github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py b/.github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py index 340fdd90e..24ec717d4 100644 --- a/.github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py +++ b/.github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py @@ -5,17 +5,18 @@ from build_assets.selenium_runner.enums import IcomoonPage, IcomoonAlerts class PeekSeleniumRunner(SeleniumRunner): - def peek(self, svgs: List[str], screenshot_folder: str): + def peek(self, svgs: List[str], screenshot_folder: str, icon_info: dict): """ Upload the SVGs and peek at how Icomoon interpret its SVGs and font versions. :param svgs: a list of svg Paths that we'll upload to icomoon. :param screenshot_folder: the name of the screenshot_folder. + :param icon_info: a dictionary containing info on an icon. Taken from the devicon.json. :return an array of svgs with strokes as strings. These show which icon contains stroke. """ messages = self.peek_svgs(svgs, screenshot_folder) - self.peek_icons(svgs, screenshot_folder) + self.peek_icons(screenshot_folder, icon_info) return messages def peek_svgs(self, svgs: List[str], screenshot_folder: str): @@ -61,10 +62,11 @@ def peek_svgs(self, svgs: List[str], screenshot_folder: str): print("Finished peeking the svgs...") return svgs_with_strokes - def peek_icons(self, svgs: List[str], screenshot_folder: str): + def peek_icons(self, screenshot_folder: str, icon_info: dict): """ Peek at the icon versions of the SVGs that were uploaded. :param screenshot_folder: the name of the screenshot_folder. + :param icon_info: a dictionary containing info on an icon. Taken from the devicon.json. """ print("Begin peeking at the icons...") # ensure all icons in the set is selected. @@ -85,7 +87,7 @@ def peek_icons(self, svgs: List[str], screenshot_folder: str): main_content = self.driver.find_element_by_xpath(main_content_xpath) main_content.screenshot(new_icons_path); - # go downward so we get the oldest icon first + # go in reverse order so we get the oldest icon first icon_divs_xpath = f'//div[@id="glyphSet0"]/div' icon_divs = self.driver.find_elements_by_xpath(icon_divs_xpath) icon_divs.reverse() @@ -98,6 +100,23 @@ def peek_icons(self, svgs: List[str], screenshot_folder: str): Path(screenshot_folder, f"new_icon_{i}.png").resolve() ) icon_div.screenshot(icon_screenshot) + + i += 1 + + # test the colors + style = "#glyphSet0 span:first-of-type {color: " + icon_info["color"] + "}" + script = f"document.styleSheets[0].insertRule('{style}', 0)" + self.driver.execute_script(script) + i = 0 + for icon_div in icon_divs: + if not icon_div.is_displayed(): + continue + + icon_screenshot = str( + Path(screenshot_folder, f"new_colored_icon_{i}.png").resolve() + ) + icon_div.screenshot(icon_screenshot) + i += 1 print("Finished peeking the icons...") diff --git a/.github/scripts/build_assets/selenium_runner/SeleniumRunner.py b/.github/scripts/build_assets/selenium_runner/SeleniumRunner.py index 7ada11631..89da771e1 100644 --- a/.github/scripts/build_assets/selenium_runner/SeleniumRunner.py +++ b/.github/scripts/build_assets/selenium_runner/SeleniumRunner.py @@ -1,6 +1,8 @@ from pathlib import Path +from selenium.webdriver.common import service from selenium.webdriver.firefox.webdriver import WebDriver +from selenium.webdriver.firefox.service import Service from selenium.webdriver.firefox.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait @@ -69,6 +71,11 @@ class SeleniumRunner: IcomoonPage.GENERATE_FONT: ICOMOON_URL + "/font" } + """ + Number of retries for creating a web driver instance. + """ + MAX_RETRY = 5 + """ The different types of alerts that this workflow will encounter. It contains part of the text in the actual alert and buttons @@ -126,6 +133,7 @@ def set_browser_options(self, download_path: str, geckodriver_path: str, :raises AssertionError: if the page title does not contain "IcoMoon App". """ + # customize the download options options = Options() allowed_mime_types = "application/zip, application/gzip, application/octet-stream" # disable prompt to download from Firefox @@ -138,15 +146,62 @@ def set_browser_options(self, download_path: str, geckodriver_path: str, options.headless = headless print("Activating browser client...") - self.driver = WebDriver(options=options, executable_path=geckodriver_path) + self.driver = self.create_driver_instance(options, geckodriver_path) + self.driver.get(self.ICOMOON_URL) - assert "IcoMoon App" in self.driver.title # wait until the whole web page is loaded by testing the hamburger input WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until( ec.element_to_be_clickable((By.XPATH, "(//i[@class='icon-menu'])[2]")) ) print("Accessed icomoon.io") + def create_driver_instance(self, options: Options, geckodriver_path: str): + """ + Create a WebDriver instance. Isolate retrying code here to address + "no connection can be made" error. + :param options: the FirefoxOptions for the browser. + :param geckodriver_path: the path to the firefox executable. + the icomoon.zip to. + """ + retries = SeleniumRunner.MAX_RETRY + finished = False + driver = None + err_msgs = [] # keep for logging purposes + while not finished and retries > 0: + try: + # order matters, don't change the lines below + finished = True # signal we are done in case we are actually done + + # customize the local server + service = None + # first retry: use 8080 + # else: random + if retries == SeleniumRunner.MAX_RETRY: + service = Service(executable_path=geckodriver_path, port=8080) + else: + service = Service(executable_path=geckodriver_path) + driver = WebDriver(options=options, service=service) + except SeleniumTimeoutException as e: + # retry. This is intended to catch "no connection could be made" error + retries -= 1 + finished = False # flip the var so we can retry + msg = f"Retry {retries}/{SeleniumRunner.MAX_RETRY} SeleniumTimeoutException: {e.msg}" + print(msg) + err_msgs.append(msg) + except Exception as e: + # anything else: unsure if retry works. Just end the retry + msg = f"Retry {retries}/{SeleniumRunner.MAX_RETRY} Exception: {e}" + err_msgs.append(msg) + print(msg) + break + + if driver is not None: + return driver + + err_msg_formatted = '\n'.join(reversed(err_msgs)) + msg = f"Unable to create WebDriver Instance:\n{err_msg_formatted}" + raise Exception(msg) + def switch_toolbar_option(self, option: IcomoonOptionState): """ Switch the toolbar option to the option argument. @@ -248,7 +303,7 @@ def edit_svg(self, screenshot_folder: str=None, index: int=None): except SeleniumTimeoutException: pass # do nothing cause sometimes, the color tab doesn't appear in the site - if screenshot_folder != None and index != None: + if screenshot_folder is not None and index is not None: edit_screen_selector = "div.overlay div.overlayWindow" screenshot_path = str( Path(screenshot_folder, f"new_svg_{index}.png").resolve() diff --git a/.github/scripts/icomoon_peek.py b/.github/scripts/icomoon_peek.py index 2dcc7143c..900f1aae4 100644 --- a/.github/scripts/icomoon_peek.py +++ b/.github/scripts/icomoon_peek.py @@ -7,17 +7,17 @@ def main(): runner = None try: args = arg_getters.get_selenium_runner_args(peek_mode=True) - new_icons = filehandler.get_json_file_content(args.devicon_json_path) + all_icons = filehandler.get_json_file_content(args.devicon_json_path) # get only the icon object that has the name matching the pr title - filtered_icon = util.find_object_added_in_pr(new_icons, args.pr_title) + filtered_icon = util.find_object_added_in_pr(all_icons, args.pr_title) check_devicon_object(filtered_icon) print("Icon being checked:", filtered_icon, sep = "\n", end='\n\n') runner = PeekSeleniumRunner(args.download_path, args.geckodriver_path, args.headless) svgs = filehandler.get_svgs_paths([filtered_icon], args.icons_folder_path, True) screenshot_folder = filehandler.create_screenshot_folder("./") - svgs_with_strokes = runner.peek(svgs, screenshot_folder) + svgs_with_strokes = runner.peek(svgs, screenshot_folder, filtered_icon) print("Task completed.") message = "" @@ -36,6 +36,7 @@ def main(): def check_devicon_object(icon: dict): """ Check that the devicon object added is up to standard. + :param icon: a dictionary containing info on an icon. Taken from the devicon.json. :return a string containing the error messages if any. """ err_msgs = [] diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 00691033d..decaef17a 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -1,2 +1,2 @@ -selenium==3.141.0 +selenium==4.1.0 requests==2.25.1 \ No newline at end of file diff --git a/.github/workflows/peek_icons.yml b/.github/workflows/peek_icons.yml index 476c08df4..3e9c56ee7 100644 --- a/.github/workflows/peek_icons.yml +++ b/.github/workflows/peek_icons.yml @@ -30,7 +30,7 @@ jobs: run: echo $PR_NUM > pr_num.txt - name: Upload the PR number - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2.2.4 with: name: pr_num path: ./pr_num.txt @@ -41,25 +41,25 @@ jobs: shell: cmd run: > python ./.github/scripts/icomoon_peek.py - ./.github/scripts/build_assets/geckodriver-v0.29.1-win64/geckodriver.exe ./icomoon.json + ./.github/scripts/build_assets/geckodriver-v0.30.0-win64/geckodriver.exe ./icomoon.json ./devicon.json ./icons ./ --headless --pr_title "%PR_TITLE%" - name: Upload the err messages (created by icomoon_peek.py) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2.2.4 if: always() with: name: err_messages path: ./err_messages.txt - name: Upload screenshots for comments - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2.2.4 if: success() with: name: screenshots path: ./screenshots/*.png - name: Upload geckodriver.log for debugging purposes - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2.2.4 if: failure() with: name: geckodriver-log diff --git a/.github/workflows/post_peek_screenshot.yml b/.github/workflows/post_peek_screenshot.yml index 5370878a2..ec010e72f 100644 --- a/.github/workflows/post_peek_screenshot.yml +++ b/.github/workflows/post_peek_screenshot.yml @@ -72,6 +72,14 @@ jobs: path: ./screenshots/new_icon_*.png client_id: ${{secrets.IMGUR_CLIENT_ID}} + - name: Upload zoomed in screenshot of the colored icons gotten from the artifacts + id: colored_icons_detailed_img_step + uses: devicons/public-upload-to-imgur@v2.2.2 + if: env.PEEK_STATUS == 'success' && success() + with: + path: ./screenshots/new_colored_icon_*.png + client_id: ${{secrets.IMGUR_CLIENT_ID}} + - name: Comment on the PR about the result - Success uses: jungwinter/comment@v1 # let us comment on a specific PR if: env.PEEK_STATUS == 'success' && success() @@ -80,20 +88,22 @@ jobs: Hi there, I'm Devicons' Peek Bot and I just peeked at the icons that you wanted to add using [icomoon.io](https://icomoon.io/app/#/select). - {0} + Here are the SVGs as intepreted by Icomoon when we upload the files: + {0} + + Here are the zoomed-in screenshots of the added icons as **SVGs**: {1} - Here are the zoomed-in screenshots of the added icons as **SVGs**. This is how Icomoon intepret the uploaded SVGs: + Here are the icons that will be generated by Icomoon: {2} - Here are the icons that will be generated by Icomoon: + Here are the zoomed-in screenshots of the added icons as **icons**: {3} - Here are the zoomed-in screenshots of the added icons as **icons**. This is what the font will look like: + Here are the colored versions: {4} - - You can click on the pictures and zoom on them if needed. + {5} The maintainers will now check for: 1. The number of Glyphs matches the number of SVGs that were selected. @@ -104,7 +114,7 @@ jobs: Thank you for contributing to Devicon! I hope that your icons are accepted into the repository. - Note: If the images don't show up, it's probably because it has been autodeleted by Imgur after 6 months due to our API choice. + Note: If the images don't show up, it has been autodeleted by Imgur after 6 months due to our API choice. Cheers, Peek Bot :blush: @@ -116,11 +126,12 @@ jobs: ${{ format( env.MESSAGE, - steps.err_message_reader.outputs.content, fromJSON(steps.svgs_overview_img_step.outputs.markdown_urls)[0], join(fromJSON(steps.svgs_detailed_img_step.outputs.markdown_urls), ' '), fromJSON(steps.icons_overview_img_step.outputs.markdown_urls)[0], - join(fromJSON(steps.icons_detailed_img_step.outputs.markdown_urls), ' ') + join(fromJSON(steps.icons_detailed_img_step.outputs.markdown_urls), ' '), + join(fromJSON(steps.colored_icons_detailed_img_step.outputs.markdown_urls), ' '), + steps.err_message_reader.outputs.content ) }}