mirror of
https://github.com/konpa/devicon.git
synced 2025-08-12 01:24:42 +02:00
New Feature: Upgrade Peekbot (#966)
* 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
This commit is contained in:
Binary file not shown.
@@ -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.
|
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.
|
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
|
BIN
.github/scripts/build_assets/geckodriver-v0.30.0-win64/geckodriver.exe
vendored
Normal file
BIN
.github/scripts/build_assets/geckodriver-v0.30.0-win64/geckodriver.exe
vendored
Normal file
Binary file not shown.
@@ -5,17 +5,18 @@ from build_assets.selenium_runner.SeleniumRunner import SeleniumRunner
|
|||||||
from build_assets.selenium_runner.enums import IcomoonPage, IcomoonAlerts
|
from build_assets.selenium_runner.enums import IcomoonPage, IcomoonAlerts
|
||||||
|
|
||||||
class PeekSeleniumRunner(SeleniumRunner):
|
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
|
Upload the SVGs and peek at how Icomoon interpret its SVGs and
|
||||||
font versions.
|
font versions.
|
||||||
:param svgs: a list of svg Paths that we'll upload to icomoon.
|
:param svgs: a list of svg Paths that we'll upload to icomoon.
|
||||||
:param screenshot_folder: the name of the screenshot_folder.
|
: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
|
:return an array of svgs with strokes as strings. These show which icon
|
||||||
contains stroke.
|
contains stroke.
|
||||||
"""
|
"""
|
||||||
messages = self.peek_svgs(svgs, screenshot_folder)
|
messages = self.peek_svgs(svgs, screenshot_folder)
|
||||||
self.peek_icons(svgs, screenshot_folder)
|
self.peek_icons(screenshot_folder, icon_info)
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def peek_svgs(self, svgs: List[str], screenshot_folder: str):
|
def peek_svgs(self, svgs: List[str], screenshot_folder: str):
|
||||||
@@ -61,10 +62,11 @@ class PeekSeleniumRunner(SeleniumRunner):
|
|||||||
print("Finished peeking the svgs...")
|
print("Finished peeking the svgs...")
|
||||||
return svgs_with_strokes
|
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.
|
Peek at the icon versions of the SVGs that were uploaded.
|
||||||
:param screenshot_folder: the name of the screenshot_folder.
|
: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...")
|
print("Begin peeking at the icons...")
|
||||||
# ensure all icons in the set is selected.
|
# 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 = self.driver.find_element_by_xpath(main_content_xpath)
|
||||||
main_content.screenshot(new_icons_path);
|
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_xpath = f'//div[@id="glyphSet0"]/div'
|
||||||
icon_divs = self.driver.find_elements_by_xpath(icon_divs_xpath)
|
icon_divs = self.driver.find_elements_by_xpath(icon_divs_xpath)
|
||||||
icon_divs.reverse()
|
icon_divs.reverse()
|
||||||
@@ -98,6 +100,23 @@ class PeekSeleniumRunner(SeleniumRunner):
|
|||||||
Path(screenshot_folder, f"new_icon_{i}.png").resolve()
|
Path(screenshot_folder, f"new_icon_{i}.png").resolve()
|
||||||
)
|
)
|
||||||
icon_div.screenshot(icon_screenshot)
|
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
|
i += 1
|
||||||
|
|
||||||
print("Finished peeking the icons...")
|
print("Finished peeking the icons...")
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from selenium.webdriver.common import service
|
||||||
|
|
||||||
from selenium.webdriver.firefox.webdriver import WebDriver
|
from selenium.webdriver.firefox.webdriver import WebDriver
|
||||||
|
from selenium.webdriver.firefox.service import Service
|
||||||
from selenium.webdriver.firefox.options import Options
|
from selenium.webdriver.firefox.options import Options
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
@@ -69,6 +71,11 @@ class SeleniumRunner:
|
|||||||
IcomoonPage.GENERATE_FONT: ICOMOON_URL + "/font"
|
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.
|
The different types of alerts that this workflow will encounter.
|
||||||
It contains part of the text in the actual alert and buttons
|
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
|
:raises AssertionError: if the page title does not contain
|
||||||
"IcoMoon App".
|
"IcoMoon App".
|
||||||
"""
|
"""
|
||||||
|
# customize the download options
|
||||||
options = Options()
|
options = Options()
|
||||||
allowed_mime_types = "application/zip, application/gzip, application/octet-stream"
|
allowed_mime_types = "application/zip, application/gzip, application/octet-stream"
|
||||||
# disable prompt to download from Firefox
|
# disable prompt to download from Firefox
|
||||||
@@ -138,15 +146,62 @@ class SeleniumRunner:
|
|||||||
options.headless = headless
|
options.headless = headless
|
||||||
|
|
||||||
print("Activating browser client...")
|
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)
|
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
|
# wait until the whole web page is loaded by testing the hamburger input
|
||||||
WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
||||||
ec.element_to_be_clickable((By.XPATH, "(//i[@class='icon-menu'])[2]"))
|
ec.element_to_be_clickable((By.XPATH, "(//i[@class='icon-menu'])[2]"))
|
||||||
)
|
)
|
||||||
print("Accessed icomoon.io")
|
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):
|
def switch_toolbar_option(self, option: IcomoonOptionState):
|
||||||
"""
|
"""
|
||||||
Switch the toolbar option to the option argument.
|
Switch the toolbar option to the option argument.
|
||||||
@@ -248,7 +303,7 @@ class SeleniumRunner:
|
|||||||
except SeleniumTimeoutException:
|
except SeleniumTimeoutException:
|
||||||
pass # do nothing cause sometimes, the color tab doesn't appear in the site
|
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"
|
edit_screen_selector = "div.overlay div.overlayWindow"
|
||||||
screenshot_path = str(
|
screenshot_path = str(
|
||||||
Path(screenshot_folder, f"new_svg_{index}.png").resolve()
|
Path(screenshot_folder, f"new_svg_{index}.png").resolve()
|
||||||
|
7
.github/scripts/icomoon_peek.py
vendored
7
.github/scripts/icomoon_peek.py
vendored
@@ -7,17 +7,17 @@ def main():
|
|||||||
runner = None
|
runner = None
|
||||||
try:
|
try:
|
||||||
args = arg_getters.get_selenium_runner_args(peek_mode=True)
|
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
|
# 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)
|
check_devicon_object(filtered_icon)
|
||||||
print("Icon being checked:", filtered_icon, sep = "\n", end='\n\n')
|
print("Icon being checked:", filtered_icon, sep = "\n", end='\n\n')
|
||||||
|
|
||||||
runner = PeekSeleniumRunner(args.download_path, args.geckodriver_path, args.headless)
|
runner = PeekSeleniumRunner(args.download_path, args.geckodriver_path, args.headless)
|
||||||
svgs = filehandler.get_svgs_paths([filtered_icon], args.icons_folder_path, True)
|
svgs = filehandler.get_svgs_paths([filtered_icon], args.icons_folder_path, True)
|
||||||
screenshot_folder = filehandler.create_screenshot_folder("./")
|
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.")
|
print("Task completed.")
|
||||||
|
|
||||||
message = ""
|
message = ""
|
||||||
@@ -36,6 +36,7 @@ def main():
|
|||||||
def check_devicon_object(icon: dict):
|
def check_devicon_object(icon: dict):
|
||||||
"""
|
"""
|
||||||
Check that the devicon object added is up to standard.
|
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.
|
:return a string containing the error messages if any.
|
||||||
"""
|
"""
|
||||||
err_msgs = []
|
err_msgs = []
|
||||||
|
2
.github/scripts/requirements.txt
vendored
2
.github/scripts/requirements.txt
vendored
@@ -1,2 +1,2 @@
|
|||||||
selenium==3.141.0
|
selenium==4.1.0
|
||||||
requests==2.25.1
|
requests==2.25.1
|
10
.github/workflows/peek_icons.yml
vendored
10
.github/workflows/peek_icons.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
run: echo $PR_NUM > pr_num.txt
|
run: echo $PR_NUM > pr_num.txt
|
||||||
|
|
||||||
- name: Upload the PR number
|
- name: Upload the PR number
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2.2.4
|
||||||
with:
|
with:
|
||||||
name: pr_num
|
name: pr_num
|
||||||
path: ./pr_num.txt
|
path: ./pr_num.txt
|
||||||
@@ -41,25 +41,25 @@ jobs:
|
|||||||
shell: cmd
|
shell: cmd
|
||||||
run: >
|
run: >
|
||||||
python ./.github/scripts/icomoon_peek.py
|
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%"
|
./devicon.json ./icons ./ --headless --pr_title "%PR_TITLE%"
|
||||||
|
|
||||||
- name: Upload the err messages (created by icomoon_peek.py)
|
- name: Upload the err messages (created by icomoon_peek.py)
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2.2.4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: err_messages
|
name: err_messages
|
||||||
path: ./err_messages.txt
|
path: ./err_messages.txt
|
||||||
|
|
||||||
- name: Upload screenshots for comments
|
- name: Upload screenshots for comments
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2.2.4
|
||||||
if: success()
|
if: success()
|
||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: ./screenshots/*.png
|
path: ./screenshots/*.png
|
||||||
|
|
||||||
- name: Upload geckodriver.log for debugging purposes
|
- name: Upload geckodriver.log for debugging purposes
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2.2.4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: geckodriver-log
|
name: geckodriver-log
|
||||||
|
29
.github/workflows/post_peek_screenshot.yml
vendored
29
.github/workflows/post_peek_screenshot.yml
vendored
@@ -72,6 +72,14 @@ jobs:
|
|||||||
path: ./screenshots/new_icon_*.png
|
path: ./screenshots/new_icon_*.png
|
||||||
client_id: ${{secrets.IMGUR_CLIENT_ID}}
|
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
|
- name: Comment on the PR about the result - Success
|
||||||
uses: jungwinter/comment@v1 # let us comment on a specific PR
|
uses: jungwinter/comment@v1 # let us comment on a specific PR
|
||||||
if: env.PEEK_STATUS == 'success' && success()
|
if: env.PEEK_STATUS == 'success' && success()
|
||||||
@@ -80,20 +88,22 @@ jobs:
|
|||||||
Hi there,
|
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).
|
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:
|
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}
|
{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}
|
{2}
|
||||||
|
|
||||||
Here are the icons that will be generated by Icomoon:
|
Here are the zoomed-in screenshots of the added icons as **icons**:
|
||||||
{3}
|
{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}
|
{4}
|
||||||
|
{5}
|
||||||
You can click on the pictures and zoom on them if needed.
|
|
||||||
|
|
||||||
The maintainers will now check for:
|
The maintainers will now check for:
|
||||||
1. The number of Glyphs matches the number of SVGs that were selected.
|
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.
|
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,
|
Cheers,
|
||||||
Peek Bot :blush:
|
Peek Bot :blush:
|
||||||
@@ -116,11 +126,12 @@ jobs:
|
|||||||
${{
|
${{
|
||||||
format(
|
format(
|
||||||
env.MESSAGE,
|
env.MESSAGE,
|
||||||
steps.err_message_reader.outputs.content,
|
|
||||||
fromJSON(steps.svgs_overview_img_step.outputs.markdown_urls)[0],
|
fromJSON(steps.svgs_overview_img_step.outputs.markdown_urls)[0],
|
||||||
join(fromJSON(steps.svgs_detailed_img_step.outputs.markdown_urls), ' '),
|
join(fromJSON(steps.svgs_detailed_img_step.outputs.markdown_urls), ' '),
|
||||||
fromJSON(steps.icons_overview_img_step.outputs.markdown_urls)[0],
|
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
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user