Merge pull request #836 from seleniumbase/update-tours-and-dependencies

Update options, tours, dependencies, and more
This commit is contained in:
Michael Mintz 2021-03-06 23:58:08 -05:00 committed by GitHub
commit e04d6d875b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 142 additions and 74 deletions

View File

@ -358,6 +358,7 @@ The code above will leave your browser window open in case there's a failure. (i
--headless # (Run tests headlessly. Default mode on Linux OS.)
--headed # (Run tests with a GUI on Linux OS.)
--locale=LOCALE_CODE # (Set the Language Locale Code for the web browser.)
--interval=SECONDS # (The autoplay interval for presentations & tour steps)
--start-page=URL # (The starting URL for the web browser when tests begin.)
--archive-logs # (Archive existing log files instead of deleting them.)
--archive-downloads # (Archive old downloads instead of deleting them.)

View File

@ -4,13 +4,13 @@ This test is only for Microsoft Edge (Chromium)!
from seleniumbase import BaseCase
class EdgeTestClass(BaseCase):
class EdgeTests(BaseCase):
def test_edge(self):
if self.browser != "edge":
print("\n This test is only for Microsoft Edge (Chromium)!")
print(" (Run with: '--browser=edge')")
self.skip("This test is only for Microsoft Edge (Chromium)!")
print(' (Run this test using "--edge" or "--browser=edge")')
self.skip('Use "--edge" or "--browser=edge"')
self.open("edge://settings/help")
self.assert_element('img[alt="Edge logo"] + span')
self.highlight('#section_about div + div')

View File

@ -1,7 +1,7 @@
<h3><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Logging, Dashboards, and Reports:</h3>
[<img src="http://img.youtube.com/vi/XpuJCjJhJwQ/0.jpg" title="SeleniumBase Features" width="285">](https://www.youtube.com/watch?v=XpuJCjJhJwQ)
<p>(<b><a href="https://www.youtube.com/watch?v=XpuJCjJhJwQ">SB Dashboard Tutorial on YouTube</a></b>)</p>
<p>(<b><a href="https://www.youtube.com/watch?v=XpuJCjJhJwQ">SBase Dashboard Tutorial on YouTube</a></b>)</p>
🔵 During test failures, logs and screenshots from the most recent test run will get saved to the ``latest_logs/`` folder. If ``--archive-logs`` is specified (or if ARCHIVE_EXISTING_LOGS is set to True in [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py)), test logs will also get archived to the ``archived_logs/`` folder. Otherwise, the log files will be cleaned out when the next test run begins (by default).

View File

@ -81,6 +81,7 @@ except (ImportError, ValueError):
sb.ad_block_on = False
sb.highlights = None
sb.check_js = False
sb.interval = None
sb.cap_file = None
sb.cap_string = None

View File

@ -28,6 +28,8 @@ class SwagLabsTests(BaseCase):
""" This test checks functional flow of the Swag Labs store.
This test is parameterized on the login user. """
self.login_to_swag_labs(username=username)
if username == "problem_user":
print("\n(This test should fail)")
# Verify that the "Test.allTheThings() T-Shirt" appears on the page
item_name = "Test.allTheThings() T-Shirt"
@ -91,6 +93,8 @@ class SwagLabsTests(BaseCase):
""" This test checks for 404s on the Swag Labs products page.
This test is parameterized on the login user. """
self.login_to_swag_labs(username=username, v1=True)
if username == "problem_user":
print("\n(This test should fail)")
self.assert_no_404_errors()
@parameterized.expand([
@ -102,6 +106,8 @@ class SwagLabsTests(BaseCase):
""" This test checks for visual regressions on the Swag Labs page.
This test is parameterized on the login user. """
self.login_to_swag_labs(username="standard_user")
if username == "problem_user":
print("\n(This test should fail)")
self.check_window(baseline=True)
self.login_to_swag_labs(username=username)
self.check_window(level=3)

View File

@ -13,7 +13,7 @@ class MyTestClass(BaseCase):
def test_deferred_asserts(self):
self.open('https://xkcd.com/993/')
self.wait_for_element('#comic')
print("\n(This test fails on purpose)")
print("\n(This test should fail)")
self.deferred_assert_element('img[alt="Brand Identity"]')
self.deferred_assert_element('img[alt="Rocket Ship"]') # Will Fail
self.deferred_assert_element('#comicmap')

View File

@ -13,5 +13,5 @@ class MyTestClass(BaseCase):
@pytest.mark.expected_failure
def test_find_army_of_robots_on_xkcd_desert_island(self):
self.open("https://xkcd.com/731/")
print("\n(This test fails on purpose)")
print("\n(This test should fail)")
self.assert_element("div#ARMY_OF_ROBOTS", timeout=1)

View File

@ -9,13 +9,13 @@ Default mobile settings for User Agent and Device Metrics if not specified:
from seleniumbase import BaseCase
class SkypeWebsiteTestClass(BaseCase):
class SkypeTests(BaseCase):
def test_skype_website_on_mobile(self):
def test_skype_mobile_site(self):
if not self.mobile_emulator:
print("\n This test is only for mobile devices / emulators!")
print(" (Usage: '--mobile' with a Chromium-based browser.)")
self.skip("Please rerun this test using '--mobile' !!!")
print("\n This test is only for mobile-device web browsers!")
print(' (Use "--mobile" to run this test in Mobile Mode!)')
self.skip('Use "--mobile" to run this test in Mobile Mode!')
self.open("https://www.skype.com/en/")
self.assert_text("Install Skype", "div.appInfo")
self.highlight("div.appBannerContent")

View File

@ -15,7 +15,7 @@ class MyTestSuite(BaseCase):
@pytest.mark.expected_failure
def test_2(self):
print("\n(This test fails on purpose)")
print("\n(This test should fail)")
self.open("https://xkcd.com/1675/")
raise Exception("FAKE EXCEPTION: This test fails on purpose.")
@ -27,6 +27,6 @@ class MyTestSuite(BaseCase):
@pytest.mark.expected_failure
def test_4(self):
print("\n(This test fails on purpose)")
print("\n(This test should fail)")
self.open("https://xkcd.com/1670/")
self.assert_element("FakeElement.DoesNotExist", timeout=0.5)

View File

@ -15,5 +15,5 @@ class MyTestClass(BaseCase):
def test_time_limit_feature(self):
self.set_time_limit(5) # Fail test if time exceeds 5 seconds
self.open("https://xkcd.com/1658/")
print("\n(This test fails on purpose)")
print("\n(This test should fail)")
self.sleep(7)

View File

@ -21,14 +21,15 @@ class MyTourClass(BaseCase):
self.play_tour()
self.highlight_update_text('input[title="Search"]', "GitHub\n")
self.ad_block()
self.wait_for_element("#search")
self.create_bootstrap_tour()
self.add_tour_step("See Results Here!", title="(5-second autoplay)")
self.add_tour_step("3-second autoplay...")
self.add_tour_step("Here's the next tour:")
self.play_tour(interval=5) # Tour automatically continues after 5 sec
self.play_tour(interval=3) # Tour automatically continues after 3 sec
self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
self.open("https://www.google.com/maps/@42.3591234,-71.0915634,15z")
self.wait_for_element("#searchboxinput", timeout=20)
self.wait_for_element("#minimap", timeout=20)
self.wait_for_element("#zoom", timeout=20)

View File

@ -14,7 +14,6 @@ class MyTestClass(BaseCase):
self.add_tour_step("Click here for the next comic.", 'a[rel="next"]')
self.add_tour_step("Click here for the previous one.", 'a[rel="prev"]')
self.add_tour_step("Learn about the author here.", 'a[rel="author"]')
self.add_tour_step("Click here for the license.", 'a[rel="license"]')
self.add_tour_step("Click for a random comic.", 'a[href*="/random/"]')
self.add_tour_step("Thanks for taking this tour!")
self.export_tour(filename="bootstrap_xkcd_tour.js") # Exports the tour

View File

@ -4,7 +4,7 @@ from seleniumbase import BaseCase
class MyTestClass(BaseCase):
def test_create_tour(self):
self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
self.open("https://www.google.com/maps/@42.3591234,-71.0915634,15z")
self.wait_for_element("#searchboxinput", timeout=20)
self.wait_for_element("#minimap", timeout=20)
self.wait_for_element("#zoom", timeout=20)

View File

@ -25,16 +25,17 @@ class MyTourClass(BaseCase):
self.play_tour()
self.highlight_update_text('input[title="Search"]', "GitHub\n")
self.ad_block()
self.wait_for_element("#search")
# Create a website tour using the Bootstrap Tour JS library
# Same as: self.create_bootstrap_tour()
self.create_tour(theme="bootstrap")
self.add_tour_step("See Results Here!", title="(5-second autoplay)")
self.add_tour_step("3-second autoplay...")
self.add_tour_step("Here's the next tour:")
self.play_tour(interval=5) # Tour automatically continues after 5 sec
self.play_tour(interval=3) # Tour automatically continues after 3 sec
self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
self.open("https://www.google.com/maps/@42.3591234,-71.0915634,15z")
self.wait_for_element("#searchboxinput")
self.wait_for_element("#minimap")
self.wait_for_element("#zoom")

View File

@ -21,14 +21,15 @@ class MyTourClass(BaseCase):
self.play_tour()
self.highlight_update_text('input[title="Search"]', "GitHub\n")
self.ad_block()
self.wait_for_element("#search")
self.create_hopscotch_tour()
self.add_tour_step("See Results Here!", title="(5-second autoplay)")
self.add_tour_step("3-second autoplay...")
self.add_tour_step("Here's the next tour:")
self.play_tour(interval=5) # Tour automatically continues after 5 sec
self.play_tour(interval=3) # Tour automatically continues after 3 sec
self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
self.open("https://www.google.com/maps/@42.3591234,-71.0915634,15z")
self.wait_for_element("#searchboxinput", timeout=20)
self.wait_for_element("#minimap", timeout=20)
self.wait_for_element("#zoom", timeout=20)

View File

@ -21,14 +21,15 @@ class MyTourClass(BaseCase):
self.play_tour()
self.highlight_update_text('input[title="Search"]', "GitHub\n")
self.ad_block()
self.wait_for_element("#search")
self.create_introjs_tour()
self.add_tour_step("See Results Here!", title="(5-second autoplay)")
self.add_tour_step("3-second autoplay...")
self.add_tour_step("Here's the next tour:")
self.play_tour(interval=5) # Tour automatically continues after 5 sec
self.play_tour(interval=3) # Tour automatically continues after 3 sec
self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
self.open("https://www.google.com/maps/@42.3591234,-71.0915634,15z")
self.wait_for_element("#searchboxinput", timeout=20)
self.wait_for_element("#minimap", timeout=20)
self.wait_for_element("#zoom", timeout=20)

View File

@ -4,7 +4,7 @@ from seleniumbase import BaseCase
class MyTourClass(BaseCase):
def test_google_maps_tour(self):
self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
self.open("https://www.google.com/maps/@42.3591234,-71.0915634,15z")
self.wait_for_element("#searchboxinput", timeout=20)
self.wait_for_element("#minimap", timeout=20)
self.wait_for_element("#zoom", timeout=20)

View File

@ -21,14 +21,15 @@ class MyTourClass(BaseCase):
self.play_tour()
self.highlight_update_text('input[title="Search"]', "GitHub\n")
self.ad_block()
self.wait_for_element("#search")
self.create_shepherd_tour(theme="square-dark")
self.add_tour_step("See Results Here!", title="(5-second autoplay)")
self.add_tour_step("3-second autoplay...")
self.add_tour_step("Here's the next tour:")
self.play_tour(interval=5) # Tour automatically continues after 5 sec
self.play_tour(interval=3) # Tour automatically continues after 3 sec
self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
self.open("https://www.google.com/maps/@42.3591234,-71.0915634,15z")
self.wait_for_element("#searchboxinput", timeout=20)
self.wait_for_element("#minimap", timeout=20)
self.wait_for_element("#zoom", timeout=20)

View File

@ -11,6 +11,7 @@ class VisualLayoutFailureTest(BaseCase):
self.click('a[href="?diff1"]')
# Click a button that makes a hidden element visible
self.click("button")
print("(This test should fail)")
self.check_window(name="helloworld", level=3)
def test_python_home_change(self):
@ -19,6 +20,7 @@ class VisualLayoutFailureTest(BaseCase):
self.check_window(name="python_home", baseline=True)
# Remove the "Donate" button
self.remove_element('a.donate-button')
print("(This test should fail)")
self.check_window(name="python_home", level=3)
def test_xkcd_logo_change(self):
@ -28,4 +30,5 @@ class VisualLayoutFailureTest(BaseCase):
# Change height: (83 -> 110) , Change width: (185 -> 120)
self.set_attribute('[alt="xkcd.com logo"]', "height", "110")
self.set_attribute('[alt="xkcd.com logo"]', "width", "120")
print("(This test should fail)")
self.check_window(name="xkcd_554", level=3)

View File

@ -116,6 +116,7 @@ SeleniumBase provides additional ``pytest`` command-line options for tests:
--headless # (Run tests headlessly. Default mode on Linux OS.)
--headed # (Run tests with a GUI on Linux OS.)
--locale=LOCALE_CODE # (Set the Language Locale Code for the web browser.)
--interval=SECONDS # (The autoplay interval for presentations & tour steps)
--start-page=URL # (The starting URL for the web browser when tests begin.)
--archive-logs # (Archive existing log files instead of deleting them.)
--archive-downloads # (Archive old downloads instead of deleting them.)

View File

@ -1,7 +1,7 @@
[<img src="https://seleniumbase.io/cdn/img/super_logo_sb.png" title="SeleniumBase" width="285">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)
[<img src="http://img.youtube.com/vi/Sjzq9kU5kOw/0.jpg" title="SeleniumBase Features" width="285">](https://www.youtube.com/watch?v=Sjzq9kU5kOw)
<p>(<b><a href="https://www.youtube.com/watch?v=Sjzq9kU5kOw">Watch the SB tutorial on YouTube!</a></b>)</p>
<p>(<b><a href="https://www.youtube.com/watch?v=Sjzq9kU5kOw">Watch the SBase tutorial on YouTube</a></b>)</p>
<a id="feature_list"></a>
<h2><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> ⛲ Features: 🗂️</h2>
@ -37,5 +37,7 @@
* Can load and make assertions on PDF files from websites or the local file system.
* Includes useful [Python decorators and password obfuscation methods](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/ReadMe.md).
--------
[<img src="http://img.youtube.com/vi/yEQeAU_mrg0/0.jpg" title="SeleniumBase Features" width="285">](https://www.youtube.com/watch?v=yEQeAU_mrg0)
<p>(<b><a href="https://www.youtube.com/watch?v=yEQeAU_mrg0">See a quick overview on YouTube!</a></b>)</p>
<p>(<b><a href="https://www.youtube.com/watch?v=yEQeAU_mrg0">See SB features in action on YouTube</a></b>)</p>

View File

@ -3,7 +3,7 @@ pip>=21.0.1;python_version>="3.6"
packaging>=20.9
setuptools>=44.1.1;python_version<"3.5"
setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"
setuptools>=54.1.0;python_version>="3.6"
setuptools>=54.1.1;python_version>="3.6"
setuptools-scm>=5.0.2
wheel>=0.36.2
attrs>=20.3.0
@ -78,8 +78,8 @@ cffi==1.14.5
toml==0.10.2
Pillow==6.2.2;python_version<"3.5"
Pillow==7.2.0;python_version>="3.5" and python_version<"3.6"
Pillow==8.1.1;python_version>="3.6"
rich==9.12.4;python_version>="3.6" and python_version<"4.0"
Pillow==8.1.2;python_version>="3.6"
rich==9.13.0;python_version>="3.6" and python_version<"4.0"
flake8==3.7.9;python_version<"3.5"
flake8==3.8.4;python_version>="3.5"
pyflakes==2.1.1;python_version<"3.5"

View File

@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "1.56.8"
__version__ = "1.57.0"

View File

@ -17,6 +17,7 @@ Format: A CSS Selector that's ready for JavaScript's querySelectorAll()
"""
AD_BLOCK_LIST = [
'[aria-label="Ads"]',
'[src*="/adservice."]',
'[src*="doubleclick.net"]',
'[src*="googletagservices.com"]',

View File

@ -101,18 +101,11 @@ style = title + '''<style type="text/css">
bt_backdrop_style = (
'''
.tour-tour-element {
box-shadow: 0 0 0 99999px rgba(0, 0, 0, 0.20);
pointer-events: none !important;
z-index: 9999;
}
:not(.tour-tour-element) .orphan.tour-tour {
box-shadow: 0 0 0 99999px rgba(0, 0, 0, 0.20);
}
.tour-tour {
pointer-events: auto;
z-index: 9999;
box-shadow: 0 0 0 88422px rgba(0, 0, 0, 0.42);
pointer-events: auto !important;
}
''')

View File

@ -3976,6 +3976,8 @@ class BaseCase(unittest.TestCase):
raise Exception('Presentation file must end in ".html"!')
if not interval:
interval = 0
if interval == 0 and self.interval:
interval = float(self.interval)
if not type(interval) is int and not type(interval) is float:
raise Exception('Expecting a numeric value for "interval"!')
if interval < 0:
@ -4046,6 +4048,8 @@ class BaseCase(unittest.TestCase):
raise Exception('Presentation file must end in ".html"!')
if not interval:
interval = 0
if interval == 0 and self.interval:
interval = float(self.interval)
if not type(interval) is int and not type(interval) is float:
raise Exception('Expecting a numeric value for "interval"!')
if interval < 0:
@ -4569,6 +4573,8 @@ class BaseCase(unittest.TestCase):
filename = "my_chart.html"
if not interval:
interval = 0
if interval == 0 and self.interval:
interval = float(self.interval)
if not type(interval) is int and not type(interval) is float:
raise Exception('Expecting a numeric value for "interval"!')
if interval < 0:
@ -4763,6 +4769,15 @@ class BaseCase(unittest.TestCase):
"""
// Bootstrap Tour
var tour = new Tour({
container: 'body',
animation: true,
keyboard: true,
orphan: true,
smartPlacement: true,
autoscroll: true,
backdrop: true,
backdropContainer: 'body',
backdropPadding: 3,
});
tour.addSteps([
""")
@ -4990,15 +5005,21 @@ class BaseCase(unittest.TestCase):
else:
duration = str(float(duration) * 1000.0)
bd = "backdrop: true,"
if selector == "html":
bd = "backdrop: false,"
step = ("""{
%s
title: '%s',
content: '%s',
orphan: true,
autoscroll: true,
%s
placement: 'auto %s',
smartPlacement: true,
duration: %s,
},""" % (element_row, title, message, alignment, duration))
},""" % (element_row, title, message, bd, alignment, duration))
self._tour_steps[name].append(step)
@ -5116,6 +5137,11 @@ class BaseCase(unittest.TestCase):
if self.headless:
return # Tours should not run in headless mode.
if not interval:
interval = 0
if interval == 0 and self.interval:
interval = float(self.interval)
if not name:
name = "default"
if name not in self._tour_steps:
@ -6655,6 +6681,7 @@ class BaseCase(unittest.TestCase):
self.headless_active = False
self.headed = sb_config.headed
self.locale_code = sb_config.locale_code
self.interval = sb_config.interval
self.start_page = sb_config.start_page
self.log_path = sb_config.log_path
self.with_testing_base = sb_config.with_testing_base

View File

@ -155,7 +155,7 @@ class HighCharts:
class BootstrapTour:
VER = "0.11.0"
VER = "0.12.0"
MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
"bootstrap-tour/%s/css/bootstrap-tour-standalone.min.css" % VER)
MIN_JS = ("https://cdnjs.cloudflare.com/ajax/libs/"

View File

@ -45,6 +45,7 @@ def pytest_addoption(parser):
--headless (Run tests headlessly. Default mode on Linux OS.)
--headed (Run tests with a GUI on Linux OS.)
--locale=LOCALE_CODE (Set the Language Locale Code for the web browser.)
--interval=SECONDS (The autoplay interval for presentations & tour steps)
--start-page=URL (The starting URL for the web browser when tests begin.)
--archive-logs (Archive existing log files instead of deleting them.)
--archive-downloads (Archive old downloads instead of deleting them.)
@ -350,6 +351,15 @@ def pytest_addoption(parser):
The Locale alters visible text on supported websites.
See: https://seleniumbase.io/help_docs/locale_codes/
Default: None. (The web browser's default mode.)""")
parser.addoption('--interval',
action='store',
dest='interval',
default=None,
help="""This globally overrides the default interval,
(in seconds), of features that include autoplay
functionality, such as tours and presentations.
Overrides from methods take priority over this.
(Headless Mode skips tours and presentations.)""")
parser.addoption('--start_page', '--start-page', '--url',
action='store',
dest='start_page',
@ -658,6 +668,7 @@ def pytest_configure(config):
sb_config.headless = config.getoption('headless')
sb_config.headed = config.getoption('headed')
sb_config.locale_code = config.getoption('locale_code')
sb_config.interval = config.getoption('interval')
sb_config.start_page = config.getoption('start_page')
sb_config.extension_zip = config.getoption('extension_zip')
sb_config.extension_dir = config.getoption('extension_dir')

View File

@ -26,6 +26,7 @@ class SeleniumBrowser(Plugin):
--headless (Run tests headlessly. Default mode on Linux OS.)
--headed (Run tests with a GUI on Linux OS.)
--locale=LOCALE_CODE (Set the Language Locale Code for the web browser.)
--interval=SECONDS (The autoplay interval for presentations & tour steps)
--start-page=URL (The starting URL for the web browser when tests begin.)
--time-limit=SECONDS (Safely fail any test that exceeds the time limit.)
--slow (Slow down the automation. Faster than using Demo Mode.)
@ -193,6 +194,16 @@ class SeleniumBrowser(Plugin):
The Locale alters visible text on supported websites.
See: https://seleniumbase.io/help_docs/locale_codes/
Default: None. (The web browser's default mode.)""")
parser.add_option(
'--interval',
action='store',
dest='interval',
default=None,
help="""This globally overrides the default interval,
(in seconds), of features that include autoplay
functionality, such as tours and presentations.
Overrides from methods take priority over this.
(Headless Mode skips tours and presentations.)""")
parser.add_option(
'--start_page', '--start-page', '--url',
action='store',
@ -405,6 +416,7 @@ class SeleniumBrowser(Plugin):
test.test.headless = self.options.headless
test.test.headed = self.options.headed
test.test.locale_code = self.options.locale_code
test.test.interval = self.options.interval
test.test.start_page = self.options.start_page
test.test.servername = self.options.servername
test.test.port = self.options.port

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -107,7 +107,7 @@ setup(
'packaging>=20.9',
'setuptools>=44.1.1;python_version<"3.5"',
'setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"',
'setuptools>=54.1.0;python_version>="3.6"',
'setuptools>=54.1.1;python_version>="3.6"',
'setuptools-scm>=5.0.2',
'wheel>=0.36.2',
'attrs>=20.3.0',
@ -182,8 +182,8 @@ setup(
'toml==0.10.2',
'Pillow==6.2.2;python_version<"3.5"',
'Pillow==7.2.0;python_version>="3.5" and python_version<"3.6"',
'Pillow==8.1.1;python_version>="3.6"',
'rich==9.12.4;python_version>="3.6" and python_version<"4.0"',
'Pillow==8.1.2;python_version>="3.6"',
'rich==9.13.0;python_version>="3.6" and python_version<"4.0"',
'flake8==3.7.9;python_version<"3.5"',
'flake8==3.8.4;python_version>="3.5"',
'pyflakes==2.1.1;python_version<"3.5"',