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 a0104767..00000000 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 008b1110..de90fe4e 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 00000000..c59764ab 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 340fdd90..24ec717d 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.SeleniumRunner import SeleniumRunner 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 @@ class PeekSeleniumRunner(SeleniumRunner): 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 @@ class PeekSeleniumRunner(SeleniumRunner): 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 @@ class PeekSeleniumRunner(SeleniumRunner): 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 7ada1163..89da771e 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 @@ class SeleniumRunner: :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 @@ class SeleniumRunner: 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 @@ class SeleniumRunner: 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 2dcc7143..900f1aae 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 00691033..decaef17 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 476c08df..3e9c56ee 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 5370878a..ec010e72 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 ) }}