diff --git a/atest/acceptance/multiple_browsers_multiple_windows.robot b/atest/acceptance/multiple_browsers_multiple_windows.robot new file mode 100644 index 000000000..4e3571f4d --- /dev/null +++ b/atest/acceptance/multiple_browsers_multiple_windows.robot @@ -0,0 +1,163 @@ +*** Setting *** +Documentation These tests must open own browser because windows opened by +... earlier tests would otherwise be visible to Get Window XXX keywords +... even if those windows were closed. +Suite Setup Open 3 Browsers with Windows +Suite Teardown Close All Browsers +Resource resource.robot + +*** Variables *** +@{BrowserA_EXP_TITLES}= WindowA1 WindowA2 WindowA3 +@{BrowserB_EXP_TITLES}= WindowB1 WindowB2 +@{BrowserC_EXP_TITLES}= WindowC1 +@{ALL_BROWSERS_EXP_TITLES}= @{BrowserA_EXP_TITLES} @{BrowserB_EXP_TITLES} @{BrowserC_EXP_TITLES} +@{EXP_ALIASES}= BrowserA BrowserB BrowserC +@{EXP_IDS}= ${1} ${2} ${3} + + +*** Test Cases *** +Check Titles of Multiple Browser-Windows + @{BrowserA_Titles}= Get Window Titles browser=BrowserA + Should Be Equal ${BrowserA_Titles} ${BrowserA_EXP_TITLES} + @{BrowserB_Titles}= Get Window Titles browser=BrowserB + Should Be Equal ${BrowserB_Titles} ${BrowserB_EXP_TITLES} + @{BrowserC_Titles}= Get Window Titles browser=BrowserC + Should Be Equal ${BrowserC_Titles} ${BrowserC_EXP_TITLES} + @{All_Browsers_Titles}= Get Window Titles browser=ALL + Should Be Equal ${All_Browsers_Titles} ${ALL_BROWSERS_EXP_TITLES} + +Check Count of Handle + Check Handle Count 3 BrowserA + Check Handle Count 2 BrowserB + Check Handle Count 1 BrowserC + Check Handle Count 6 ALL + +Check Count of Names + Check Name Count 3 BrowserA + Check Name Count 2 BrowserB + Check Name Count 1 BrowserC + Check Name Count 6 ALL + +Check Count of Identifiers + Check Identifiers Count 3 BrowserA + Check Identifiers Count 2 BrowserB + Check Identifiers Count 1 BrowserC + Check Identifiers Count 6 ALL + +Check Locations + @{Locations}= Get Locations browser=ALL + Should Be Equal As Strings @{Locations}[0] ${FRONT_PAGE}javascript/dynamic_content.html?1 + Should Be Equal As Strings @{Locations}[1] ${FRONT_PAGE}javascript/dynamic_content.html?2 + Should Be Equal As Strings @{Locations}[2] ${FRONT_PAGE}javascript/dynamic_content.html?3 + Should Be Equal As Strings @{Locations}[3] ${FRONT_PAGE}javascript/dynamic_content.html?4 + Should Be Equal As Strings @{Locations}[4] ${FRONT_PAGE}javascript/dynamic_content.html?5 + Should Be Equal As Strings @{Locations}[5] ${FRONT_PAGE}javascript/dynamic_content.html?6 + ${count} Get Length ${Locations} + Should Be Equal As Integers 6 ${count} + +Get Browser Ids and Alias + @{Aliases}= Get Browser Aliases + Should Be Equal ${Aliases} ${EXP_ALIASES} + &{Aliases}= Get Browser Aliases + Should Be Equal ${Aliases.BrowserA} ${1} + Should Be Equal ${Aliases.BrowserB} ${2} + Should Be Equal ${Aliases.BrowserC} ${3} + @{IDs}= Get Browser Ids + Should Be Equal ${IDs} ${EXP_IDS} + +Select Window by Location + Switch Browser BrowserA + Switch Window WindowA1 + Switch Window By Location ${FRONT_PAGE}javascript/dynamic_content.html?5 + ${location} Get Location + Should Be Equal ${FRONT_PAGE}javascript/dynamic_content.html?5 ${location} + Title Should Be WindowB2 + +Switch Window to Different Browser + Switch Browser BrowserC + Switch Window WindowC1 + Location Should Be ${FRONT_PAGE}javascript/dynamic_content.html?6 + Switch Window title:WindowA1 browser=BrowserA + Location Should Be ${FRONT_PAGE}javascript/dynamic_content.html?1 + Switch Window url:${FRONT_PAGE}javascript/dynamic_content.html?4 browser=BrowserB + Title Should Be WindowB1 + +Get Specific Locations and Title + Switch Browser BrowserA + Switch Window title:WindowA1 + Location Should Be ${FRONT_PAGE}javascript/dynamic_content.html?1 + @{Locations}= Get Locations browser=BrowserB + Should Be Equal @{Locations}[0] ${FRONT_PAGE}javascript/dynamic_content.html?4 + Should Be Equal @{Locations}[1] ${FRONT_PAGE}javascript/dynamic_content.html?5 + ${count}= Get Length ${Locations} + Should Be Equal As Integers ${count} 2 + @{Titles}= Get Window Titles browser=BrowserC + Should Be Equal @{Titles}[0] WindowC1 + ${count}= Get Length ${Titles} + Should Be Equal As Integers ${count} 1 + + +Fail Switching Window and Locations From Different Browser + Switch Browser BrowserA + Switch Window WindowA1 + ${Error_Msg}= Run Keyword And Expect Error * Switch Window WindowB1 + Should Be Equal As Strings ${Error_Msg} No window matching handle, name, title or URL 'WindowB1' found. + ${Error_Msg}= Run Keyword And Expect Error * Get Locations browser=UnknownBrowser + Should Be Equal As Strings ${Error_Msg} Non-existing index or alias 'UnknownBrowser'. + ${Error_Msg}= Run Keyword And Expect Error * Get Window Handles browser=${4} + Should Be Equal As Strings ${Error_Msg} Non-existing index or alias '4'. + + + +*** Keywords *** +Open 3 Browsers with Windows + Close All Browsers + Open Browser With Alias And Title BrowserA WindowA1 1 + Open New Window and set Title WindowA2 2 + Open New Window And Set Title WindowA3 3 + Open Browser With Alias And Title BrowserB WindowB1 4 + Open New Window And Set Title WindowB2 5 + Open Browser With Alias And Title BrowserC WindowC1 6 + +Open New Window And Set Title + [Arguments] ${title} ${id} + Execute Javascript window.open("dynamic_content.html?${id}") + Switch Window locator=NEW + Set Window Title ${title} + +Open Browser With Alias And Title + [Arguments] ${alias} ${title} ${id} + Open Browser ${FRONT_PAGE}javascript/dynamic_content.html?${id} ${BROWSER} alias=${alias} + Set Window Title ${title} + +Set Window Title + [Arguments] ${title} + Input Text id:titleChangeTxt ${title} + Click Button id:titleChangeBtn + Title Should Be ${title} + +Check Handle Count + [Arguments] ${length} ${browser_alias}=${None} + @{WinCountBrowser}= Get Window Handles ${browser_alias} + ${len}= Get Length ${WinCountBrowser} + Should Be Equal As Integers ${len} ${length} + +Check Name Count + [Arguments] ${length} ${browser_alias}=${None} + @{WinCountBrowser}= Get Window Names ${browser_alias} + ${len}= Get Length ${WinCountBrowser} + Should Be Equal As Integers ${len} ${length} + +Check Identifiers Count + [Arguments] ${length} ${browser_alias}=${None} + @{WinCountBrowser}= Get Window Identifiers ${browser_alias} + ${len}= Get Length ${WinCountBrowser} + Should Be Equal As Integers ${len} ${length} + +Switch Window By Location + [Arguments] ${selected_location} + @{IDs}= Get Browser Ids + :FOR ${id} IN @{IDs} + \ @{locations}= Get Locations browser=${id} + \ Run Keyword If '${selected_location}' in $locations + ... Switch Window url:${selected_location} browser=${id} diff --git a/atest/resources/html/javascript/dynamic_content.html b/atest/resources/html/javascript/dynamic_content.html index 396fed74b..284d5d4ee 100644 --- a/atest/resources/html/javascript/dynamic_content.html +++ b/atest/resources/html/javascript/dynamic_content.html @@ -17,10 +17,10 @@ add content
title to ääää

- Change Title
- Add Content
+ Change Title
+ Add Content

@@ -30,8 +30,18 @@ - - + +

+

+ + + + + + +
+

diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index d8bee30c0..0a53d434a 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -62,6 +62,7 @@ class SeleniumLibrary(DynamicCore): == Table of contents == - `Locating elements` + - `Browser and Window` - `Timeouts, waits and delays` - `Run-on-failure functionality` - `Boolean arguments` @@ -223,6 +224,75 @@ class SeleniumLibrary(DynamicCore): See the `Add Location Strategy` keyword for more details. + = Browser and Window = + + There is different conseptual meaning when SeleniumLibrary talks + windows and browsers. This chapter explains those differences. + + == Browser == + + When `Open Browser` or `Create WebDriver` keyword is called, it + will create a new Selenium WebDriver instance by using the + [https://www.seleniumhq.org/docs/03_webdriver.jsp|Selenium WebDriver] + API. In SeleniumLibrary terms, a new broser is created. It is + possible to start multiple independent browsers (Selenium Webdriver + instances) at the same time, by calling `Open Browser` or + `Create WebDriver` multiple times. These browsers are usually + independent to each other and do not share data like cookies, + sessions or profiles. Typicall when browser starts, it + creates a single window in the desktop. + + == Window == + + Windows are the part of a browser that loads the web site and presents + it to the user. All content of the site is content of the window. + Windows are children of a WebDriver instance, in SeleniumLibrary + WebDriver is referred as browser. One browser may have multiple + windows. Windows can appear as tabs or as separate windows with + different position and size. Windows belonning to the same browser + typically share the sessions detail, like cookies. If there is a + need to separate sessions detail, example login with two different + users, two browser (Selenium WebDriver instances) must be created. + New windows can be opened example by the application under test or + by example `Execute Javascript` keyword: + + | `Execute Javascript` window.open() # Opens a new window with location about:blank + + In the example in below opens multiple browser and windows, + to demonstrate how the different keywords can be used to interact + with a browser and windows atteched to the browser. + + Structure: + | BrowserA + | Window 1 (location=https://robotframework.org/) + | Window 2 (location=https://robocon.io/) + | Window 3 (location=https://github.com/robotframework/) + | + | BrowserB + | Window 1 (location=https://github.com/) + + Example: + | `Open Browser` | https://robotframework.org | ${BROWSER} | alias=BrowserA | # BrowserA with first window is opened. | + | `Execute Javascript` | window.open() | | | # In BrowserA second window is opened. | + | `Switch Window` | locator=NEW | | | # Switched to second window in BrowserA | + | `Go To` | https://robocon.io | | | # Second window navigates to to robocon site. | + | `Execute Javascript` | window.open() | | | # In BrowserA third window is opened. | + | ${handle} | `Switch Window` | locator=NEW | | # Switched to third window in BrowserA | + | `Go To` | https://github.com/robotframework/ | | | # Third windows goes to robot framework github site. | + | `Open Browser` | https://github.com | ${BROWSER} | alias=BrowserB | # BrowserB with first windows is opened. | + | ${location} | `Get Location` | | | # ${location} is: https://www.github.com | + | `Switch Window` | ${handle} | browser=BrowserA | | # BrowserA second windows is selected. | + | ${location} | `Get Location` | | | # ${location} = https://robocon.io/ | + | @{locations 1} | `Get Locations` | | | # By default lists locations under the currectly active browser. | + | @{locations 2} | `Get Locations` | browser=ALL | | # By using browser=ALL argument keyword list all locations from all browsers. | + + The above example, @{locations 1} contains the following items: + https://robotframework.org/, https://robocon.io/ and + https://github.com/robotframework/'. The @{locations 2} + contains the following items: https://robotframework.org/, + https://robocon.io/, https://github.com/robotframework/' + and 'https://github.com/. + = Timeouts, waits and delays = This section discusses different ways how to wait for elements to diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 14c76efcc..8a197b50a 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -180,7 +180,7 @@ def open_browser(self, url, browser='firefox', alias=None, | options = webdriver.ChromeOptions() | options.add_argument('--disable-dev-shm-usage') | return options - + Then the `${options}` variable can be used as argument to ``options``. @@ -350,6 +350,44 @@ def switch_browser(self, index_or_alias): self.debug('Switched to browser with Selenium session id %s.' % self.driver.session_id) + @keyword + def get_browser_ids(self): + """Returns index of all active browser as list. + + Example: + | @{browser_ids}= | Get Browser Ids | | | + | FOR | ${id} | IN | @{browser_ids} | + | | @{window_titles}= | Get Window Titles | browser=${id} | + | | Log | Browser ${id} has these windows: ${window_titles} | | + | END | | | | + + See `Switch Browser` for more information and examples. + + New in SeleniumLibrary 4.0 + """ + return self.drivers.active_driver_ids + + @keyword + def get_browser_aliases(self): + """Returns aliases of all active browser that has an alias as NormalizedDict. + The dictionary contains the aliases as keys and the index as value. + This can be accessed as dictionary ``${aliases.key}`` or as list ``@{aliases}[0]``. + + Example: + | `Open Browser` | https://example.com | alias=BrowserA | | + | `Open Browser` | https://example.com | alias=BrowserB | | + | &{aliases} | `Get Browser Aliases` | | # &{aliases} = { BrowserA=1|BrowserB=2 } | + | `Log` | ${aliases.BrowserA} | | # logs ``1`` | + | FOR | ${alias} | IN | @{aliases} | + | | `Log` | ${alias} | # logs ``BrowserA`` and ``BrowserB`` | + | END | | | | + + See `Switch Browser` for more information and examples. + + New in SeleniumLibrary 4.0 + """ + return self.drivers.active_aliases + @keyword def get_session_id(self): """Returns the currently active browser session id. @@ -370,7 +408,7 @@ def get_title(self): @keyword def get_location(self): - """Returns the current browser URL.""" + """Returns the current browser window URL.""" return self.driver.current_url @keyword @@ -413,7 +451,7 @@ def location_should_contain(self, expected, message=None): @keyword def log_location(self): - """Logs and returns the current URL.""" + """Logs and returns the current browser window URL.""" url = self.get_location() self.info(url) return url @@ -460,7 +498,7 @@ def go_back(self): @keyword def go_to(self, url): - """Navigates the active browser instance to the provided ``url``.""" + """Navigates the current browser window to the provided ``url``.""" self.info("Opening url '%s'" % url) self.driver.get(url) diff --git a/src/SeleniumLibrary/keywords/cookie.py b/src/SeleniumLibrary/keywords/cookie.py index efff19e29..3d74a898a 100644 --- a/src/SeleniumLibrary/keywords/cookie.py +++ b/src/SeleniumLibrary/keywords/cookie.py @@ -52,7 +52,7 @@ def get_cookies(self, as_dict=False): sending HTTP requests. The dictionary format is helpful when the result can be passed to requests library's Create Session keyword's optional cookies parameter. - + The `` as_dict`` argument is new in SeleniumLibrary 3.3 """ if is_falsy(as_dict): diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index 880ea390c..317d9c16a 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -435,12 +435,12 @@ def get_element_size(self, locator): @keyword def cover_element(self, locator): """Will cover elements identified by ``locator`` with a blue div without breaking page layout. - + See the `Locating elements` section for details about the locator syntax. - + New in SeleniumLibrary 3.3.0 - + Example: |`Cover Element` | css:div#container | """ diff --git a/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py index c64e657cb..889a88f56 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py @@ -301,6 +301,18 @@ def active_drivers(self): open_drivers.append(driver) return open_drivers + @property + def active_driver_ids(self): + open_driver_ids = [] + for index, driver in enumerate(self._connections): + if driver not in self._closed: + open_driver_ids.append(index + 1) + return open_driver_ids + + @property + def active_aliases(self): + return self._aliases + def close(self): if self.current: driver = self.current diff --git a/src/SeleniumLibrary/keywords/window.py b/src/SeleniumLibrary/keywords/window.py index a572402af..14ed82332 100644 --- a/src/SeleniumLibrary/keywords/window.py +++ b/src/SeleniumLibrary/keywords/window.py @@ -20,7 +20,7 @@ from SeleniumLibrary.base import keyword, LibraryComponent from SeleniumLibrary.locators import WindowManager -from SeleniumLibrary.utils import plural_or_not +from SeleniumLibrary.utils import plural_or_not, is_string class WindowKeywords(LibraryComponent): @@ -31,17 +31,19 @@ def __init__(self, ctx): @keyword def select_window(self, locator='MAIN', timeout=None): - """Selects browser window matching ``locator``. + """DEPRECATED in SeleniumLibrary 4.0. , use `Switch Window` instead.""" + return self.switch_window(locator, timeout) + + @keyword + def switch_window(self, locator='MAIN', timeout=None, browser='CURRENT'): + """Switches to browser window matching ``locator``. If the window is found, all subsequent commands use the selected window, until this keyword is used again. If the window is not found, this keyword fails. The previous window handle is returned, and can be used to return back to it later. - Notice that in this context _window_ means a pop-up window opened - when doing something on an existing window. It is not possible to - select windows opened with `Open Browser`, `Switch Browser` must - be used instead. Notice also that alerts should be handled with + Notice that alerts should be handled with `Handle Alert` or other alert related keywords. The ``locator`` can be specified using different strategies somewhat @@ -77,20 +79,26 @@ def select_window(self, locator='MAIN', timeout=None): Example: | `Click Link` | popup1 | | # Open new window | - | `Select Window` | example | | # Select window using default strategy | + | `Switch Window` | example | | # Select window using default strategy | | `Title Should Be` | Pop-up 1 | | | `Click Button` | popup2 | | # Open another window | - | ${handle} = | `Select Window` | NEW | # Select latest opened window | + | ${handle} = | `Switch Window` | NEW | # Select latest opened window | | `Title Should Be` | Pop-up 2 | | - | `Select Window` | ${handle} | | # Select window using handle | + | `Switch Window` | ${handle} | | # Select window using handle | | `Title Should Be` | Pop-up 1 | | - | `Select Window` | MAIN | | # Select the main window | + | `Switch Window` | MAIN | | # Select the main window | | `Title Should Be` | Main | | | ${excludes} = | `Get Window Handles` | | # Get list of current windows | | `Click Link` | popup3 | | # Open one more window | - | `Select Window` | ${excludes} | | # Select window using excludes | + | `Switch Window` | ${excludes} | | # Select window using excludes | | `Title Should Be` | Pop-up 3 | | + The ``browser`` argument allows with ``index_or_alias`` to implicitly switch to + a specific browser when switching to a window. See `Switch Browser` + + - If the ``browser`` is ``CURRENT`` (case-insensitive), no other browser is + selected. + *NOTE:* - The ``strategy:value`` syntax is only supported by SeleniumLibrary @@ -109,45 +117,68 @@ def select_window(self, locator='MAIN', timeout=None): except NoSuchWindowException: pass finally: + if not is_string(browser) or not browser.upper() == 'CURRENT': + self.drivers.switch(browser) self._window_manager.select(locator, timeout) @keyword def close_window(self): - """Closes currently opened pop-up window.""" + """Closes currently opened and selected browser window/tab. """ self.driver.close() @keyword - def get_window_handles(self): - """Return all current window handles as a list. + def get_window_handles(self, browser='CURRENT'): + """Returns all child window handles of the selected browser as a list. Can be used as a list of windows to exclude with `Select Window`. + How to select the ``browser`` scope of this keyword, see `Get Locations`. + Prior to SeleniumLibrary 3.0, this keyword was named `List Windows`. """ - return self.driver.window_handles + return self._window_manager.get_window_handles(browser) @keyword - def get_window_identifiers(self): - """Returns and logs id attributes of all known browser windows.""" - ids = [info.id for info in self._window_manager.get_window_infos()] + def get_window_identifiers(self, browser='CURRENT'): + """Returns and logs id attributes of all windows of the selected browser. + + How to select the ``browser`` scope of this keyword, see `Get Locations`.""" + ids = [info.id for info in self._window_manager.get_window_infos(browser)] return self._log_list(ids) @keyword - def get_window_names(self): - """Returns and logs names of all known browser windows.""" - names = [info.name for info in self._window_manager.get_window_infos()] + def get_window_names(self, browser='CURRENT'): + """Returns and logs names of all windows of the selected browser. + + How to select the ``browser`` scope of this keyword, see `Get Locations`.""" + names = [info.name for info in self._window_manager.get_window_infos(browser)] return self._log_list(names) @keyword - def get_window_titles(self): - """Returns and logs titles of all known browser windows.""" - titles = [info.title for info in self._window_manager.get_window_infos()] + def get_window_titles(self, browser='CURRENT'): + """Returns and logs titles of all windows of the selected browser. + + How to select the ``browser`` scope of this keyword, see `Get Locations`.""" + titles = [info.title for info in self._window_manager.get_window_infos(browser)] return self._log_list(titles) @keyword - def get_locations(self): - """Returns and logs URLs of all known browser windows.""" - urls = [info.url for info in self._window_manager.get_window_infos()] + def get_locations(self, browser='CURRENT'): + """Returns and logs URLs of all windows of the selected browser. + + *Browser Scope:* + + The ``browser`` argument specifies the browser that shall return + its windows information. + + - ``browser`` can be ``index_or_alias`` like in `Switch Browser`. + + - If ``browser`` is ``CURRENT`` (default, case-insensitive) + the currently active browser is selected. + + - If ``browser`` is ``ALL`` (case-insensitive) + the window information of all windows of all opened browsers are returned.""" + urls = [info.url for info in self._window_manager.get_window_infos(browser)] return self._log_list(urls) @keyword diff --git a/src/SeleniumLibrary/locators/windowmanager.py b/src/SeleniumLibrary/locators/windowmanager.py index 72cdf4fc3..be2e4135a 100644 --- a/src/SeleniumLibrary/locators/windowmanager.py +++ b/src/SeleniumLibrary/locators/windowmanager.py @@ -39,7 +39,45 @@ def __init__(self, ctx): 'default': self._select_by_default } - def get_window_infos(self): + def get_window_handles(self, browser): + if is_string(browser) and browser == 'ALL': + handles = [] + current_index = self.drivers.current_index + for index, driver in enumerate(self.drivers, 1): + self.drivers.switch(index) + handles.extend(self.driver.window_handles) + self.drivers.switch(current_index) + return handles + elif is_string(browser) and browser == 'CURRENT': + return self.driver.window_handles + else: + current_index = self.drivers.current_index + self.drivers.switch(browser) + handles = self.driver.window_handles + self.drivers.switch(current_index) + return handles + + def get_window_infos(self, browser='CURRENT'): + try: + current_index = self.drivers.current_index + except AttributeError: + current_index = None + if is_string(browser) and browser.upper() == 'ALL': + infos = [] + for index, driver in enumerate(self.drivers, 1): + self.drivers.switch(index) + infos.extend(self._get_window_infos()) + self.drivers.switch(current_index) + return infos + elif is_string(browser) and browser.upper() == 'CURRENT': + return self._get_window_infos() + else: + self.drivers.switch(browser) + infos = self._get_window_infos() + self.drivers.switch(current_index) + return infos + + def _get_window_infos(self): infos = [] try: starting_handle = self.driver.current_window_handle diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py index 277e4501b..b594866c3 100644 --- a/utest/test/api/test_plugins.py +++ b/utest/test/api/test_plugins.py @@ -22,7 +22,7 @@ def setUpClass(cls): def test_no_libraries(self): for item in [None, 'None', '']: sl = SeleniumLibrary(plugins=item) - self.assertEqual(len(sl.get_keyword_names()), 170) + self.assertEqual(len(sl.get_keyword_names()), 173) def test_parse_library(self): plugin = 'path.to.MyLibrary'