From 34391720fe0f940de96bea9638e394367f495f49 Mon Sep 17 00:00:00 2001 From: atthaboons Date: Mon, 27 Jul 2020 22:44:13 +0700 Subject: [PATCH 1/7] init browser management async --- Examples/browser-demo.robot | 6 +- PuppeteerLibrary/__init__.py | 2 + PuppeteerLibrary/keywords/__init__.py | 1 + .../keywords/browsermanagement.py | 75 +++++++++---------- .../keywords/browsermanagement_async.py | 23 ++++++ 5 files changed, 64 insertions(+), 43 deletions(-) create mode 100644 PuppeteerLibrary/keywords/browsermanagement_async.py diff --git a/Examples/browser-demo.robot b/Examples/browser-demo.robot index a04b959..3096057 100644 --- a/Examples/browser-demo.robot +++ b/Examples/browser-demo.robot @@ -8,11 +8,13 @@ Example switch browser and browser title ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} Open browser http://127.0.0.1:7272 options=${options} + 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 + ... Click Element xpath://a[@href="docs.html"] AND + ... Wait for new window open Switch Window NEW ${Title} = Get Title should be equal as strings Docs Page ${Title} diff --git a/PuppeteerLibrary/__init__.py b/PuppeteerLibrary/__init__.py index 8d02623..e302884 100644 --- a/PuppeteerLibrary/__init__.py +++ b/PuppeteerLibrary/__init__.py @@ -9,6 +9,7 @@ AlertKeywords, AlertKeywordsAsync, BrowserManagementKeywords, + BrowserManagementKeywordsAsync, ElementKeywords, ElementKeywordsAsync, FormElementKeywords, @@ -91,6 +92,7 @@ def __init__(self): self.async_libraries = [ AlertKeywordsAsync(self), + BrowserManagementKeywordsAsync(self), ElementKeywordsAsync(self), FormElementKeywordsAsync(self), JavascriptKeywordsAsync(self), 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..6b6e8cd 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,31 +38,33 @@ 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() + 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) + context = await self.ctx.browser.createIncognitoBrowserContext() + self.ctx.current_page = await context.newPage() await self.ctx.current_page.goto(url) await self.ctx.current_page.screenshot({'path': 'example.png'}) self.loop.run_until_complete(open_browser_async()) @@ -68,6 +75,7 @@ def close_browser(self): """ async def close_browser_async(): await self.ctx.browser.close() + self.ctx.browser = None self.loop.run_until_complete(close_browser_async()) @keyword @@ -145,22 +153,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'): diff --git a/PuppeteerLibrary/keywords/browsermanagement_async.py b/PuppeteerLibrary/keywords/browsermanagement_async.py new file mode 100644 index 0000000..422fe43 --- /dev/null +++ b/PuppeteerLibrary/keywords/browsermanagement_async.py @@ -0,0 +1,23 @@ +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)) From 7f4fa63040f0efbf20c79882a7b286ebf2d9f917 Mon Sep 17 00:00:00 2001 From: atthaboons Date: Mon, 27 Jul 2020 22:51:09 +0700 Subject: [PATCH 2/7] Add support open browser context with specific alias --- Examples/browser-demo.robot | 1 - PuppeteerLibrary/keywords/browsermanagement.py | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Examples/browser-demo.robot b/Examples/browser-demo.robot index 3096057..c27a8d5 100644 --- a/Examples/browser-demo.robot +++ b/Examples/browser-demo.robot @@ -8,7 +8,6 @@ Example switch browser and browser title ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} Open browser http://127.0.0.1:7272 options=${options} - Open browser http://127.0.0.1:7272 options=${options} Maximize Browser Window ${title} = Get title ${location} = Get location diff --git a/PuppeteerLibrary/keywords/browsermanagement.py b/PuppeteerLibrary/keywords/browsermanagement.py index 6b6e8cd..173149d 100644 --- a/PuppeteerLibrary/keywords/browsermanagement.py +++ b/PuppeteerLibrary/keywords/browsermanagement.py @@ -12,6 +12,7 @@ class BrowserManagementKeywords(LibraryComponent): def __init__(self, ctx): self.ctx = ctx self.async_func = BrowserManagementKeywordsAsync(self.ctx) + self.ctx.contexts = {} @keyword def open_browser(self, url, browser="chrome", alias=None, options=None): @@ -64,6 +65,10 @@ async def open_browser_async(): }, args=default_args) context = await self.ctx.browser.createIncognitoBrowserContext() + if alias in self.ctx.contexts.keys(): + await self.ctx.contexts[alias].close(); + del self.ctx.contexts[alias] + self.ctx.contexts[alias] = context self.ctx.current_page = await context.newPage() await self.ctx.current_page.goto(url) await self.ctx.current_page.screenshot({'path': 'example.png'}) From 0aff825fbded12a4beda1c17890c5a8cfb78cb4e Mon Sep 17 00:00:00 2001 From: atthaboons Date: Mon, 27 Jul 2020 23:13:21 +0700 Subject: [PATCH 3/7] init close specific browser --- PuppeteerLibrary/keywords/browsermanagement.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/PuppeteerLibrary/keywords/browsermanagement.py b/PuppeteerLibrary/keywords/browsermanagement.py index 173149d..829c7ea 100644 --- a/PuppeteerLibrary/keywords/browsermanagement.py +++ b/PuppeteerLibrary/keywords/browsermanagement.py @@ -69,6 +69,7 @@ async def open_browser_async(): await self.ctx.contexts[alias].close(); del self.ctx.contexts[alias] self.ctx.contexts[alias] = context + self.ctx.current_context_name = alias self.ctx.current_page = await context.newPage() await self.ctx.current_page.goto(url) await self.ctx.current_page.screenshot({'path': 'example.png'}) @@ -79,10 +80,13 @@ def close_browser(self): """Closes the current browser """ async def close_browser_async(): - await self.ctx.browser.close() - self.ctx.browser = None + await self.ctx.contexts[self.ctx.current_context_name].close() + del self.ctx.contexts[self.ctx.current_context_name] + # await self.ctx.browser.close() + # self.ctx.browser = None self.loop.run_until_complete(close_browser_async()) + @keyword def maximize_browser_window(self, width=1366, height=768): """Maximize view port not actual browser and set default size to 1366 x 768 From b8f7633a54854bb93d86f8ba567028bb13348320 Mon Sep 17 00:00:00 2001 From: atthaboons Date: Tue, 28 Jul 2020 09:17:52 +0700 Subject: [PATCH 4/7] Update close browser and close all browser --- Examples/browser-demo.robot | 3 + PuppeteerLibrary/__init__.py | 59 +++++++++++++++++-- .../keywords/browsermanagement.py | 29 ++++----- .../keywords/browsermanagement_async.py | 17 ++++++ 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/Examples/browser-demo.robot b/Examples/browser-demo.robot index c27a8d5..5522bfa 100644 --- a/Examples/browser-demo.robot +++ b/Examples/browser-demo.robot @@ -17,3 +17,6 @@ Example switch browser and browser title Switch Window NEW ${Title} = Get Title should be equal as strings Docs Page ${Title} + +Example muti browser + diff --git a/PuppeteerLibrary/__init__.py b/PuppeteerLibrary/__init__.py index e302884..5159297 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 @@ -70,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' @@ -107,6 +110,52 @@ 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 + def set_current_context(self, context_name): + self.current_context_name = context_name + self.current_page = self.get_current_context().pages()[-1] + + @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 @@ -120,8 +169,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/browsermanagement.py b/PuppeteerLibrary/keywords/browsermanagement.py index 829c7ea..5d00d3c 100644 --- a/PuppeteerLibrary/keywords/browsermanagement.py +++ b/PuppeteerLibrary/keywords/browsermanagement.py @@ -12,7 +12,6 @@ class BrowserManagementKeywords(LibraryComponent): def __init__(self, ctx): self.ctx = ctx self.async_func = BrowserManagementKeywordsAsync(self.ctx) - self.ctx.contexts = {} @keyword def open_browser(self, url, browser="chrome", alias=None, options=None): @@ -64,28 +63,23 @@ async def open_browser_async(): 'height': merged_options['height'] }, args=default_args) - context = await self.ctx.browser.createIncognitoBrowserContext() - if alias in self.ctx.contexts.keys(): - await self.ctx.contexts[alias].close(); - del self.ctx.contexts[alias] - self.ctx.contexts[alias] = context - self.ctx.current_context_name = alias - self.ctx.current_page = await context.newPage() - await self.ctx.current_page.goto(url) - await self.ctx.current_page.screenshot({'path': 'example.png'}) + 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.contexts[self.ctx.current_context_name].close() - del self.ctx.contexts[self.ctx.current_context_name] - # await self.ctx.browser.close() - # self.ctx.browser = None - 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 maximize_browser_window(self, width=1366, height=768): @@ -194,4 +188,3 @@ async def switch_window_async(): raise Exception('Can\'t find specify page locator.') self.loop.run_until_complete(switch_window_async()) - diff --git a/PuppeteerLibrary/keywords/browsermanagement_async.py b/PuppeteerLibrary/keywords/browsermanagement_async.py index 422fe43..a641e5d 100644 --- a/PuppeteerLibrary/keywords/browsermanagement_async.py +++ b/PuppeteerLibrary/keywords/browsermanagement_async.py @@ -21,3 +21,20 @@ async def wait_for_new_window_open_async(self, timeout=None): 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[self.ctx.current_context_name].close() + self.ctx.clear_context(alias) + if len(self.ctx.contexts.keys()) > 0: + self.ctx.set_current_context(self.contexts.keys()[-1]) + + @keyword + async def close_all_browser_async(self): + for context in self.contexts: + await context.close() + self.ctx.contexts = {} + self.ctx.current_context_name = None + self.ctx.current_page = None From 9f26744fef5e51353151cd6a54530c76e3ef6b0b Mon Sep 17 00:00:00 2001 From: atthaboons Date: Tue, 28 Jul 2020 10:32:47 +0700 Subject: [PATCH 5/7] Fix close all browser keyword --- Examples/browser-demo.robot | 19 +++++++++++++++---- PuppeteerLibrary/__init__.py | 7 +++++-- .../keywords/browsermanagement.py | 10 ++++++++++ .../keywords/browsermanagement_async.py | 11 ++++++++--- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/Examples/browser-demo.robot b/Examples/browser-demo.robot index 5522bfa..6b83835 100644 --- a/Examples/browser-demo.robot +++ b/Examples/browser-demo.robot @@ -1,10 +1,10 @@ *** 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} @@ -18,5 +18,16 @@ Example switch browser and browser title ${Title} = Get Title should be equal as strings Docs Page ${Title} -Example muti browser - +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 + diff --git a/PuppeteerLibrary/__init__.py b/PuppeteerLibrary/__init__.py index 5159297..dfae467 100644 --- a/PuppeteerLibrary/__init__.py +++ b/PuppeteerLibrary/__init__.py @@ -136,9 +136,12 @@ def get_current_context(self) -> BrowserContext: return self.contexts[self.current_context_name] @not_keyword - def set_current_context(self, context_name): + async def set_current_context(self, context_name) -> BrowserContext: self.current_context_name = context_name - self.current_page = self.get_current_context().pages()[-1] + context = self.get_current_context() + pages = await context.pages() + self.current_page = pages[-1] + return context @not_keyword def clear_context(self, context_name): diff --git a/PuppeteerLibrary/keywords/browsermanagement.py b/PuppeteerLibrary/keywords/browsermanagement.py index 5d00d3c..c4c72eb 100644 --- a/PuppeteerLibrary/keywords/browsermanagement.py +++ b/PuppeteerLibrary/keywords/browsermanagement.py @@ -81,6 +81,10 @@ def close_all_browser(self): """ 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): """Maximize view port not actual browser and set default size to 1366 x 768 @@ -188,3 +192,9 @@ 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 index a641e5d..41bd14c 100644 --- a/PuppeteerLibrary/keywords/browsermanagement_async.py +++ b/PuppeteerLibrary/keywords/browsermanagement_async.py @@ -26,15 +26,20 @@ async def wait_for_new_window_open_async(self, timeout=None): async def close_browser_async(self, alias=None): if alias is None: alias = self.ctx.current_context_name - await self.ctx.contexts[self.ctx.current_context_name].close() + await self.ctx.contexts[alias].close() self.ctx.clear_context(alias) if len(self.ctx.contexts.keys()) > 0: - self.ctx.set_current_context(self.contexts.keys()[-1]) + await self.ctx.set_current_context(list(self.ctx.contexts.keys())[-1]) @keyword async def close_all_browser_async(self): - for context in self.contexts: + 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() From 556b4fe193b3d058ff22bf8d9ed198b7bec229c5 Mon Sep 17 00:00:00 2001 From: atthaboons Date: Tue, 28 Jul 2020 10:35:42 +0700 Subject: [PATCH 6/7] Add demo close puppeteer browser keyword --- Examples/browser-demo.robot | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Examples/browser-demo.robot b/Examples/browser-demo.robot index 6b83835..731d177 100644 --- a/Examples/browser-demo.robot +++ b/Examples/browser-demo.robot @@ -31,3 +31,11 @@ Example open multiple browser 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 From 39fae6cae276f20bb732983aa759a16da1ec956e Mon Sep 17 00:00:00 2001 From: atthaboons Date: Tue, 28 Jul 2020 10:43:15 +0700 Subject: [PATCH 7/7] Update example --- Examples/browser-demo.robot | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Examples/browser-demo.robot b/Examples/browser-demo.robot index 731d177..bd318f7 100644 --- a/Examples/browser-demo.robot +++ b/Examples/browser-demo.robot @@ -11,9 +11,9 @@ Example switch window and check window title Maximize Browser Window ${title} = Get title ${location} = Get location - Run Async Keywords - ... Click Element xpath://a[@href="docs.html"] AND - ... 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}