diff --git a/Examples/browser-demo.robot b/Examples/browser-demo.robot index a04b959..bd318f7 100644 --- a/Examples/browser-demo.robot +++ b/Examples/browser-demo.robot @@ -1,18 +1,41 @@ *** Settings *** Library PuppeteerLibrary -Test Teardown Close Browser *** Test Cases *** -Example switch browser and browser title +Example switch window and check window title + [Teardown] Close Browser ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} Open browser http://127.0.0.1:7272 options=${options} Maximize Browser Window ${title} = Get title ${location} = Get location - Click Element xpath://a[@href="docs.html"] - Wait for new window open + Run Async Keywords + ... Wait for new window open AND + ... Click Element xpath://a[@href="docs.html"] Switch Window NEW ${Title} = Get Title should be equal as strings Docs Page ${Title} + +Example open multiple browser + [Teardown] Close All Browser + ${HEADLESS} Get variable value ${HEADLESS} ${False} + &{options} = create dictionary headless=${HEADLESS} + Open browser http://127.0.0.1:7272 options=${options} alias=Browser 1 + Click Element id=login_button + Open browser http://127.0.0.1:7272 options=${options} alias=Browser 2 + Click Element id=get_ajax + Switch Browser Browser 1 + Wait Until Page Contains Error Page + Switch Browser Browser 2 + Wait Until Page Contains products + +Example close puppeteer browser + [Teardown] Close All Browser + ${HEADLESS} Get variable value ${HEADLESS} ${False} + &{options} = create dictionary headless=${HEADLESS} + Open browser http://127.0.0.1:7272 options=${options} alias=Browser 1 + Close Puppeteer + Open browser http://127.0.0.1:7272 options=${options} alias=Browser 2 + Click Element id=get_ajax diff --git a/PuppeteerLibrary/__init__.py b/PuppeteerLibrary/__init__.py index 8d02623..dfae467 100644 --- a/PuppeteerLibrary/__init__.py +++ b/PuppeteerLibrary/__init__.py @@ -1,7 +1,7 @@ import asyncio from robot.api.deco import not_keyword from robot.api import logger -from pyppeteer.browser import Browser +from pyppeteer.browser import Browser, BrowserContext from robot.libraries.BuiltIn import BuiltIn from PuppeteerLibrary.custom_elements.SPage import SPage from PuppeteerLibrary.base.robotlibcore import DynamicCore @@ -9,6 +9,7 @@ AlertKeywords, AlertKeywordsAsync, BrowserManagementKeywords, + BrowserManagementKeywordsAsync, ElementKeywords, ElementKeywordsAsync, FormElementKeywords, @@ -69,11 +70,14 @@ class PuppeteerLibrary(DynamicCore): ROBOT_LISTENER_API_VERSION = 3 loop = asyncio.get_event_loop() - browser = None - current_page = None is_load_async_keywords = False async_libraries = [] + browser = None + contexts = {} + current_context_name = None + current_page = None + def __init__(self): self.run_on_failure_keyword = 'Capture Page Screenshot' @@ -91,6 +95,7 @@ def __init__(self): self.async_libraries = [ AlertKeywordsAsync(self), + BrowserManagementKeywordsAsync(self), ElementKeywordsAsync(self), FormElementKeywordsAsync(self), JavascriptKeywordsAsync(self), @@ -105,6 +110,55 @@ def load_async_keywords(self): self.add_library_components(self.async_libraries) self.is_load_async_keywords = True + @not_keyword + def get_browser(self) -> Browser: + return self.browser + + @not_keyword + def clear_browser(self): + self.browser = None + self.contexts = {} + self.current_context_name = None + self.current_page = None + + @not_keyword + async def create_context_async(self, alias) -> BrowserContext: + context = await self.browser.createIncognitoBrowserContext() + if alias in self.contexts.keys(): + await self.contexts[alias].close() + del self.contexts[alias] + self.current_context_name = alias + self.contexts[self.current_context_name] = context + return context + + @not_keyword + def get_current_context(self) -> BrowserContext: + return self.contexts[self.current_context_name] + + @not_keyword + async def set_current_context(self, context_name) -> BrowserContext: + self.current_context_name = context_name + context = self.get_current_context() + pages = await context.pages() + self.current_page = pages[-1] + return context + + @not_keyword + def clear_context(self, context_name): + del self.contexts[context_name] + if self.current_context_name == context_name: + self.current_context_name = None + self.current_page = None + + @not_keyword + def clear_current_context(self): + self.clear_context(self.current_context_name) + + @not_keyword + async def create_page_async(self) -> SPage: + self.current_page = await self.get_current_context().newPage() + return self.get_current_page() + @not_keyword def get_current_page(self) -> SPage: page = self.current_page @@ -118,8 +172,8 @@ def set_current_page(self, page) -> SPage: return self.current_page @not_keyword - def get_browser(self) -> Browser: - return self.browser + def clear_current_page(self): + self.current_page = None @not_keyword def run_keyword(self, name, args, kwargs): diff --git a/PuppeteerLibrary/keywords/__init__.py b/PuppeteerLibrary/keywords/__init__.py index 2c402e7..377272e 100644 --- a/PuppeteerLibrary/keywords/__init__.py +++ b/PuppeteerLibrary/keywords/__init__.py @@ -1,6 +1,7 @@ from .alert import AlertKeywords from .alert_async import AlertKeywordsAsync from .browsermanagement import BrowserManagementKeywords +from .browsermanagement_async import BrowserManagementKeywordsAsync from .element import ElementKeywords from .element_async import ElementKeywordsAsync from .formelement import FormElementKeywords diff --git a/PuppeteerLibrary/keywords/browsermanagement.py b/PuppeteerLibrary/keywords/browsermanagement.py index 795b5bd..c4c72eb 100644 --- a/PuppeteerLibrary/keywords/browsermanagement.py +++ b/PuppeteerLibrary/keywords/browsermanagement.py @@ -4,10 +4,15 @@ from pyppeteer import launch from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword +from PuppeteerLibrary.keywords.browsermanagement_async import BrowserManagementKeywordsAsync class BrowserManagementKeywords(LibraryComponent): + def __init__(self, ctx): + self.ctx = ctx + self.async_func = BrowserManagementKeywordsAsync(self.ctx) + @keyword def open_browser(self, url, browser="chrome", alias=None, options=None): """Opens a new browser instance to the specific ``url``. @@ -33,42 +38,52 @@ def open_browser(self, url, browser="chrome", alias=None, options=None): """ async def open_browser_async(): - default_args = [] - default_options = { - 'headless': True, - 'width': 1366, - 'height': 768 - } - merged_options = None - if options is None: - merged_options = default_options - else: - merged_options = {**default_options, **options} - - if 'win' not in sys.platform.lower(): - default_args = ['--no-sandbox', '--disable-setuid-sandbox'] - - self.info(('Open browser to ' + url + '\n' + - str(merged_options))) - self.ctx.browser = await launch( - headless=merged_options['headless'], - defaultViewport={ - 'width': merged_options['width'], - 'height': merged_options['height'] - }, - args=default_args) - self.ctx.current_page = await self.ctx.browser.newPage() - await self.ctx.current_page.goto(url) - await self.ctx.current_page.screenshot({'path': 'example.png'}) + if self.ctx.browser is None: + default_args = [] + default_options = { + 'headless': True, + 'width': 1366, + 'height': 768 + } + merged_options = None + if options is None: + merged_options = default_options + else: + merged_options = {**default_options, **options} + + if 'win' not in sys.platform.lower(): + default_args = ['--no-sandbox', '--disable-setuid-sandbox'] + + self.info(('Open browser to ' + url + '\n' + + str(merged_options))) + self.ctx.browser = await launch( + headless=merged_options['headless'], + defaultViewport={ + 'width': merged_options['width'], + 'height': merged_options['height'] + }, + args=default_args) + await self.ctx.create_context_async(alias) + current_page = await self.ctx.create_page_async() + await current_page.goto(url) + await current_page.screenshot({'path': 'example.png'}) self.loop.run_until_complete(open_browser_async()) @keyword - def close_browser(self): + def close_browser(self, alias=None): """Closes the current browser """ - async def close_browser_async(): - await self.ctx.browser.close() - self.loop.run_until_complete(close_browser_async()) + self.loop.run_until_complete(self.async_func.close_browser_async(alias)) + + @keyword + def close_all_browser(self): + """Close all browser + """ + self.loop.run_until_complete(self.async_func.close_all_browser_async()) + + @keyword + def close_puppeteer(self): + self.loop.run_until_complete(self.async_func.close_puppeteer_async()) @keyword def maximize_browser_window(self, width=1366, height=768): @@ -145,22 +160,7 @@ def wait_for_new_window_open(self, timeout=None): | Run Async Keywords | Click Element | id:view_conditions | AND | | ... | `Wait For New Window Open` | | | """ - timeout = self.timestr_to_secs_for_default_timeout(timeout) - async def wait_for_new_page_open_async(): - pages = await self.ctx.get_browser().pages() - await pages[-1].title() # workaround for force pages re-cache - pre_page_len = len(pages) - timer = 0 - while timer < timeout: - pages = await self.ctx.get_browser().pages() - await pages[-1].title() # workaround for force pages re-cache - page_len = len(pages) - if page_len > pre_page_len: - return - timer += 1 - time.sleep(1) - raise Exception('No new page has been open. pre: '+str(pre_page_len)+' current: '+str(page_len)) - self.loop.run_until_complete(wait_for_new_page_open_async()) + self.loop.run_until_complete(self.async_func.wait_for_new_window_open_async(timeout)) @keyword def switch_window(self, locator='MAIN'): @@ -193,3 +193,8 @@ async def switch_window_async(): raise Exception('Can\'t find specify page locator.') self.loop.run_until_complete(switch_window_async()) + @keyword + def switch_browser(self, alias): + """Switch browser context based on alias name + """ + return self.loop.run_until_complete(self.ctx.set_current_context(alias)) diff --git a/PuppeteerLibrary/keywords/browsermanagement_async.py b/PuppeteerLibrary/keywords/browsermanagement_async.py new file mode 100644 index 0000000..41bd14c --- /dev/null +++ b/PuppeteerLibrary/keywords/browsermanagement_async.py @@ -0,0 +1,45 @@ +import time +from PuppeteerLibrary.base.robotlibcore import keyword +from PuppeteerLibrary.base.librarycomponent import LibraryComponent + + +class BrowserManagementKeywordsAsync(LibraryComponent): + + @keyword + async def wait_for_new_window_open_async(self, timeout=None): + timeout = self.timestr_to_secs_for_default_timeout(timeout) + pages = await self.ctx.get_browser().pages() + await pages[-1].title() # workaround for force pages re-cache + pre_page_len = len(pages) + timer = 0 + while timer < timeout: + pages = await self.ctx.get_browser().pages() + await pages[-1].title() # workaround for force pages re-cache + page_len = len(pages) + if page_len > pre_page_len: + return + timer += 1 + time.sleep(1) + raise Exception('No new page has been open. pre: ' + str(pre_page_len) + ' current: ' + str(page_len)) + + @keyword + async def close_browser_async(self, alias=None): + if alias is None: + alias = self.ctx.current_context_name + await self.ctx.contexts[alias].close() + self.ctx.clear_context(alias) + if len(self.ctx.contexts.keys()) > 0: + await self.ctx.set_current_context(list(self.ctx.contexts.keys())[-1]) + + @keyword + async def close_all_browser_async(self): + for context in self.ctx.contexts.values(): + await context.close() + self.ctx.contexts = {} + self.ctx.current_context_name = None + self.ctx.current_page = None + + @keyword + async def close_puppeteer_async(self): + await self.ctx.browser.close() + self.ctx.clear_browser()