Skip to content

Commit

Permalink
New Feature: Upgrade Peekbot (devicons#966)
Browse files Browse the repository at this point in the history
* Moved stroke detection down

* Add code to retry no connection in peekbot

* Change upload artifact to earlier version

* Clean up logging msg

* Add ability to customize port number

* Update Selenium and geckodriver

* Move upload-artifact version to 2.2.4

* Add logging for webdriver retry

* Add color checking to peek-bot
  • Loading branch information
Thomas-Boi authored Dec 21, 2021
1 parent 0678c1e commit e1ece0e
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 26 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Binary file not shown.
27 changes: 23 additions & 4 deletions .github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand All @@ -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()
Expand All @@ -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...")
61 changes: 58 additions & 3 deletions .github/scripts/build_assets/selenium_runner/SeleniumRunner.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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()
Expand Down
7 changes: 4 additions & 3 deletions .github/scripts/icomoon_peek.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand All @@ -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 = []
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
selenium==3.141.0
selenium==4.1.0
requests==2.25.1
10 changes: 5 additions & 5 deletions .github/workflows/peek_icons.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
29 changes: 20 additions & 9 deletions .github/workflows/post_peek_screenshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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.
Expand All @@ -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:
Expand All @@ -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
)
}}
Expand Down

0 comments on commit e1ece0e

Please sign in to comment.