Skip to content

Commit e1ece0e

Browse files
authored
New Feature: Upgrade Peekbot (devicons#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
1 parent 0678c1e commit e1ece0e

File tree

9 files changed

+112
-26
lines changed

9 files changed

+112
-26
lines changed
Binary file not shown.

.github/scripts/build_assets/geckodriver-v0.29.1-win64/README.md renamed to .github/scripts/build_assets/geckodriver-v0.30.0-win64/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ Proxy for using W3C WebDriver compatible clients to interact with Gecko-based br
33
This program provides the HTTP API described by the WebDriver protocol to communicate with Gecko browsers, such as Firefox.
44
It translates calls into the Marionette remote protocol by acting as a proxy between the local- and remote ends.
55

6-
This application was taken from: https://github.com/mozilla/geckodriver/releases/tag/v0.29.1
6+
This application was taken from: https://github.com/mozilla/geckodriver/releases/tag/v0.30.0
Binary file not shown.

.github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@
55
from build_assets.selenium_runner.enums import IcomoonPage, IcomoonAlerts
66

77
class PeekSeleniumRunner(SeleniumRunner):
8-
def peek(self, svgs: List[str], screenshot_folder: str):
8+
def peek(self, svgs: List[str], screenshot_folder: str, icon_info: dict):
99
"""
1010
Upload the SVGs and peek at how Icomoon interpret its SVGs and
1111
font versions.
1212
:param svgs: a list of svg Paths that we'll upload to icomoon.
1313
:param screenshot_folder: the name of the screenshot_folder.
14+
:param icon_info: a dictionary containing info on an icon. Taken from the devicon.json.
1415
:return an array of svgs with strokes as strings. These show which icon
1516
contains stroke.
1617
"""
1718
messages = self.peek_svgs(svgs, screenshot_folder)
18-
self.peek_icons(svgs, screenshot_folder)
19+
self.peek_icons(screenshot_folder, icon_info)
1920
return messages
2021

2122
def peek_svgs(self, svgs: List[str], screenshot_folder: str):
@@ -61,10 +62,11 @@ def peek_svgs(self, svgs: List[str], screenshot_folder: str):
6162
print("Finished peeking the svgs...")
6263
return svgs_with_strokes
6364

64-
def peek_icons(self, svgs: List[str], screenshot_folder: str):
65+
def peek_icons(self, screenshot_folder: str, icon_info: dict):
6566
"""
6667
Peek at the icon versions of the SVGs that were uploaded.
6768
:param screenshot_folder: the name of the screenshot_folder.
69+
:param icon_info: a dictionary containing info on an icon. Taken from the devicon.json.
6870
"""
6971
print("Begin peeking at the icons...")
7072
# ensure all icons in the set is selected.
@@ -85,7 +87,7 @@ def peek_icons(self, svgs: List[str], screenshot_folder: str):
8587
main_content = self.driver.find_element_by_xpath(main_content_xpath)
8688
main_content.screenshot(new_icons_path);
8789

88-
# go downward so we get the oldest icon first
90+
# go in reverse order so we get the oldest icon first
8991
icon_divs_xpath = f'//div[@id="glyphSet0"]/div'
9092
icon_divs = self.driver.find_elements_by_xpath(icon_divs_xpath)
9193
icon_divs.reverse()
@@ -98,6 +100,23 @@ def peek_icons(self, svgs: List[str], screenshot_folder: str):
98100
Path(screenshot_folder, f"new_icon_{i}.png").resolve()
99101
)
100102
icon_div.screenshot(icon_screenshot)
103+
104+
i += 1
105+
106+
# test the colors
107+
style = "#glyphSet0 span:first-of-type {color: " + icon_info["color"] + "}"
108+
script = f"document.styleSheets[0].insertRule('{style}', 0)"
109+
self.driver.execute_script(script)
110+
i = 0
111+
for icon_div in icon_divs:
112+
if not icon_div.is_displayed():
113+
continue
114+
115+
icon_screenshot = str(
116+
Path(screenshot_folder, f"new_colored_icon_{i}.png").resolve()
117+
)
118+
icon_div.screenshot(icon_screenshot)
119+
101120
i += 1
102121

103122
print("Finished peeking the icons...")

.github/scripts/build_assets/selenium_runner/SeleniumRunner.py

+58-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from pathlib import Path
2+
from selenium.webdriver.common import service
23

34
from selenium.webdriver.firefox.webdriver import WebDriver
5+
from selenium.webdriver.firefox.service import Service
46
from selenium.webdriver.firefox.options import Options
57
from selenium.webdriver.common.by import By
68
from selenium.webdriver.support.ui import WebDriverWait
@@ -69,6 +71,11 @@ class SeleniumRunner:
6971
IcomoonPage.GENERATE_FONT: ICOMOON_URL + "/font"
7072
}
7173

74+
"""
75+
Number of retries for creating a web driver instance.
76+
"""
77+
MAX_RETRY = 5
78+
7279
"""
7380
The different types of alerts that this workflow will encounter.
7481
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,
126133
:raises AssertionError: if the page title does not contain
127134
"IcoMoon App".
128135
"""
136+
# customize the download options
129137
options = Options()
130138
allowed_mime_types = "application/zip, application/gzip, application/octet-stream"
131139
# disable prompt to download from Firefox
@@ -138,15 +146,62 @@ def set_browser_options(self, download_path: str, geckodriver_path: str,
138146
options.headless = headless
139147

140148
print("Activating browser client...")
141-
self.driver = WebDriver(options=options, executable_path=geckodriver_path)
149+
self.driver = self.create_driver_instance(options, geckodriver_path)
150+
142151
self.driver.get(self.ICOMOON_URL)
143-
assert "IcoMoon App" in self.driver.title
144152
# wait until the whole web page is loaded by testing the hamburger input
145153
WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
146154
ec.element_to_be_clickable((By.XPATH, "(//i[@class='icon-menu'])[2]"))
147155
)
148156
print("Accessed icomoon.io")
149157

158+
def create_driver_instance(self, options: Options, geckodriver_path: str):
159+
"""
160+
Create a WebDriver instance. Isolate retrying code here to address
161+
"no connection can be made" error.
162+
:param options: the FirefoxOptions for the browser.
163+
:param geckodriver_path: the path to the firefox executable.
164+
the icomoon.zip to.
165+
"""
166+
retries = SeleniumRunner.MAX_RETRY
167+
finished = False
168+
driver = None
169+
err_msgs = [] # keep for logging purposes
170+
while not finished and retries > 0:
171+
try:
172+
# order matters, don't change the lines below
173+
finished = True # signal we are done in case we are actually done
174+
175+
# customize the local server
176+
service = None
177+
# first retry: use 8080
178+
# else: random
179+
if retries == SeleniumRunner.MAX_RETRY:
180+
service = Service(executable_path=geckodriver_path, port=8080)
181+
else:
182+
service = Service(executable_path=geckodriver_path)
183+
driver = WebDriver(options=options, service=service)
184+
except SeleniumTimeoutException as e:
185+
# retry. This is intended to catch "no connection could be made" error
186+
retries -= 1
187+
finished = False # flip the var so we can retry
188+
msg = f"Retry {retries}/{SeleniumRunner.MAX_RETRY} SeleniumTimeoutException: {e.msg}"
189+
print(msg)
190+
err_msgs.append(msg)
191+
except Exception as e:
192+
# anything else: unsure if retry works. Just end the retry
193+
msg = f"Retry {retries}/{SeleniumRunner.MAX_RETRY} Exception: {e}"
194+
err_msgs.append(msg)
195+
print(msg)
196+
break
197+
198+
if driver is not None:
199+
return driver
200+
201+
err_msg_formatted = '\n'.join(reversed(err_msgs))
202+
msg = f"Unable to create WebDriver Instance:\n{err_msg_formatted}"
203+
raise Exception(msg)
204+
150205
def switch_toolbar_option(self, option: IcomoonOptionState):
151206
"""
152207
Switch the toolbar option to the option argument.
@@ -248,7 +303,7 @@ def edit_svg(self, screenshot_folder: str=None, index: int=None):
248303
except SeleniumTimeoutException:
249304
pass # do nothing cause sometimes, the color tab doesn't appear in the site
250305

251-
if screenshot_folder != None and index != None:
306+
if screenshot_folder is not None and index is not None:
252307
edit_screen_selector = "div.overlay div.overlayWindow"
253308
screenshot_path = str(
254309
Path(screenshot_folder, f"new_svg_{index}.png").resolve()

.github/scripts/icomoon_peek.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ def main():
77
runner = None
88
try:
99
args = arg_getters.get_selenium_runner_args(peek_mode=True)
10-
new_icons = filehandler.get_json_file_content(args.devicon_json_path)
10+
all_icons = filehandler.get_json_file_content(args.devicon_json_path)
1111

1212
# get only the icon object that has the name matching the pr title
13-
filtered_icon = util.find_object_added_in_pr(new_icons, args.pr_title)
13+
filtered_icon = util.find_object_added_in_pr(all_icons, args.pr_title)
1414
check_devicon_object(filtered_icon)
1515
print("Icon being checked:", filtered_icon, sep = "\n", end='\n\n')
1616

1717
runner = PeekSeleniumRunner(args.download_path, args.geckodriver_path, args.headless)
1818
svgs = filehandler.get_svgs_paths([filtered_icon], args.icons_folder_path, True)
1919
screenshot_folder = filehandler.create_screenshot_folder("./")
20-
svgs_with_strokes = runner.peek(svgs, screenshot_folder)
20+
svgs_with_strokes = runner.peek(svgs, screenshot_folder, filtered_icon)
2121
print("Task completed.")
2222

2323
message = ""
@@ -36,6 +36,7 @@ def main():
3636
def check_devicon_object(icon: dict):
3737
"""
3838
Check that the devicon object added is up to standard.
39+
:param icon: a dictionary containing info on an icon. Taken from the devicon.json.
3940
:return a string containing the error messages if any.
4041
"""
4142
err_msgs = []

.github/scripts/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
selenium==3.141.0
1+
selenium==4.1.0
22
requests==2.25.1

.github/workflows/peek_icons.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
run: echo $PR_NUM > pr_num.txt
3131

3232
- name: Upload the PR number
33-
uses: actions/upload-artifact@v2
33+
uses: actions/upload-artifact@v2.2.4
3434
with:
3535
name: pr_num
3636
path: ./pr_num.txt
@@ -41,25 +41,25 @@ jobs:
4141
shell: cmd
4242
run: >
4343
python ./.github/scripts/icomoon_peek.py
44-
./.github/scripts/build_assets/geckodriver-v0.29.1-win64/geckodriver.exe ./icomoon.json
44+
./.github/scripts/build_assets/geckodriver-v0.30.0-win64/geckodriver.exe ./icomoon.json
4545
./devicon.json ./icons ./ --headless --pr_title "%PR_TITLE%"
4646
4747
- name: Upload the err messages (created by icomoon_peek.py)
48-
uses: actions/upload-artifact@v2
48+
uses: actions/upload-artifact@v2.2.4
4949
if: always()
5050
with:
5151
name: err_messages
5252
path: ./err_messages.txt
5353

5454
- name: Upload screenshots for comments
55-
uses: actions/upload-artifact@v2
55+
uses: actions/upload-artifact@v2.2.4
5656
if: success()
5757
with:
5858
name: screenshots
5959
path: ./screenshots/*.png
6060

6161
- name: Upload geckodriver.log for debugging purposes
62-
uses: actions/upload-artifact@v2
62+
uses: actions/upload-artifact@v2.2.4
6363
if: failure()
6464
with:
6565
name: geckodriver-log

.github/workflows/post_peek_screenshot.yml

+20-9
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ jobs:
7272
path: ./screenshots/new_icon_*.png
7373
client_id: ${{secrets.IMGUR_CLIENT_ID}}
7474

75+
- name: Upload zoomed in screenshot of the colored icons gotten from the artifacts
76+
id: colored_icons_detailed_img_step
77+
uses: devicons/[email protected]
78+
if: env.PEEK_STATUS == 'success' && success()
79+
with:
80+
path: ./screenshots/new_colored_icon_*.png
81+
client_id: ${{secrets.IMGUR_CLIENT_ID}}
82+
7583
- name: Comment on the PR about the result - Success
7684
uses: jungwinter/comment@v1 # let us comment on a specific PR
7785
if: env.PEEK_STATUS == 'success' && success()
@@ -80,20 +88,22 @@ jobs:
8088
Hi there,
8189
8290
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).
83-
{0}
91+
8492
Here are the SVGs as intepreted by Icomoon when we upload the files:
93+
{0}
94+
95+
Here are the zoomed-in screenshots of the added icons as **SVGs**:
8596
{1}
8697
87-
Here are the zoomed-in screenshots of the added icons as **SVGs**. This is how Icomoon intepret the uploaded SVGs:
98+
Here are the icons that will be generated by Icomoon:
8899
{2}
89100
90-
Here are the icons that will be generated by Icomoon:
101+
Here are the zoomed-in screenshots of the added icons as **icons**:
91102
{3}
92103
93-
Here are the zoomed-in screenshots of the added icons as **icons**. This is what the font will look like:
104+
Here are the colored versions:
94105
{4}
95-
96-
You can click on the pictures and zoom on them if needed.
106+
{5}
97107
98108
The maintainers will now check for:
99109
1. The number of Glyphs matches the number of SVGs that were selected.
@@ -104,7 +114,7 @@ jobs:
104114
105115
Thank you for contributing to Devicon! I hope that your icons are accepted into the repository.
106116
107-
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.
117+
Note: If the images don't show up, it has been autodeleted by Imgur after 6 months due to our API choice.
108118
109119
Cheers,
110120
Peek Bot :blush:
@@ -116,11 +126,12 @@ jobs:
116126
${{
117127
format(
118128
env.MESSAGE,
119-
steps.err_message_reader.outputs.content,
120129
fromJSON(steps.svgs_overview_img_step.outputs.markdown_urls)[0],
121130
join(fromJSON(steps.svgs_detailed_img_step.outputs.markdown_urls), ' '),
122131
fromJSON(steps.icons_overview_img_step.outputs.markdown_urls)[0],
123-
join(fromJSON(steps.icons_detailed_img_step.outputs.markdown_urls), ' ')
132+
join(fromJSON(steps.icons_detailed_img_step.outputs.markdown_urls), ' '),
133+
join(fromJSON(steps.colored_icons_detailed_img_step.outputs.markdown_urls), ' '),
134+
steps.err_message_reader.outputs.content
124135
)
125136
}}
126137

0 commit comments

Comments
 (0)