diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 915efb8..54d1c92 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -9,7 +9,14 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.6, 3.7, 3.8] + # python-version: [3.7, 3.8, 3.9] + include: + - python-version: 3.7 + browser: webkit + - python-version: 3.8 + browser: chrome + - python-version: 3.9 + browser: firefox steps: - uses: actions/checkout@v1 @@ -21,6 +28,35 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt + - name: Install webkit + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + gstreamer1.0-libav \ + gstreamer1.0-plugins-bad \ + libgstreamer-plugins-bad1.0-0 \ + libwoff1 \ + libopus0 \ + libwebp6 \ + libwebpdemux2 \ + libenchant1c2a \ + libgudev-1.0-0 \ + libsecret-1-0 \ + libhyphen0 \ + libgdk-pixbuf2.0-0 \ + libegl1 \ + libnotify4 \ + libxslt1.1 \ + libevent-2.1-6 \ + libgles2 \ + libvpx5 \ + libxcomposite1 \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libepoxy0 \ + libgtk-3-0 \ + libharfbuzz-icu0 \ + xvfb - name: Lint with flake8 run: | pip install flake8 @@ -28,10 +64,15 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: System test + - name: Run test project run: | python setup.py install - pyppeteer-install pip install -r demoapp/requirements.txt + pyppeteer-install + python -m playwright install python demoapp/server.py & - robot -v HEADLESS:True -e Ignore Examples + - name: System test + uses: GabrielBB/xvfb-action@v1 + with: + working-directory: ./ #optional + run: robot -v BROWSER:${{ matrix.browser }} -e IgnoreORIgnore_${{ matrix.browser }} Examples diff --git a/Examples/alert-handler/alert-handler.robot b/Examples/alert-handler/alert-handler.robot index f8eabe0..7f8ed1c 100644 --- a/Examples/alert-handler/alert-handler.robot +++ b/Examples/alert-handler/alert-handler.robot @@ -4,25 +4,31 @@ Test Setup Open browser to test page Test Teardown Close All Browser Suite Teardown Close Puppeteer + *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html + *** Test Cases *** Accept alert + [Tags] Ignore_webkit Ignore_firefox Run Async Keywords ... Handle Alert ACCEPT AND - ... Click Button id=alert_confirm + ... Click Element id=alert_confirm Click Element id:get_ajax Dismiss alert + [Tags] Ignore_webkit Ignore_firefox Run Async Keywords ... Handle Alert DISMISS AND - ... Click Button id=alert_confirm + ... Click Element id=alert_confirm Click Element id:get_ajax + *** Keywords *** Open browser to test page + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} - + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/api/mock-api.robot b/Examples/api/mock-api.robot index ca01128..bc8d65c 100644 --- a/Examples/api/mock-api.robot +++ b/Examples/api/mock-api.robot @@ -5,24 +5,27 @@ Test Teardown Close All Browser Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html *** Test Cases *** - Mock ajax response with raw text +Mock ajax response with raw text &{response} Create Dictionary body=I'm a mock response text - Mock Current Page Api Response /ajax_info.json\\?count=3 ${response} + Mock Current Page Api Response /ajax_info.json?count=3 ${response} Click Element id=get_ajax Wait Until Page Contains I'm a mock response text Mock ajax response with json response &{response} Create Dictionary body={ 'data': 'I\'m a mock response json'} contentType=application/json - Mock Current Page Api Response /ajax_info.json\\?count=3 ${response} + Mock Current Page Api Response /ajax_info.json?count=3 ${response} Click Element id=get_ajax Wait Until Page Contains I'm a mock response json + *** Keywords *** Open browser to test page - ${HEADLESS} Get variable value ${HEADLESS} ${False} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/browser-management/emulator-mode.robot b/Examples/browser-management/emulator-mode.robot index bf35332..33e1614 100644 --- a/Examples/browser-management/emulator-mode.robot +++ b/Examples/browser-management/emulator-mode.robot @@ -2,11 +2,16 @@ Library PuppeteerLibrary Suite Teardown Close Puppeteer +*** Variables *** +${DEFAULT_BROWSER} chrome +# ${DEFAULT_BROWSER} webkit + *** Test Cases *** Example enable emulator mode + [Tags] Ignore_firefox [Teardown] Close All Browser - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser http://127.0.0.1:7272/basic-html-elements.html options=${options} alias=Browser 1 - Enable emulate mode iPhone SE + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} + &{options} = create dictionary headless=${HEADLESS} emulate=iPhone 11 + Open browser http://127.0.0.1:7272/basic-html-elements.html browser=${BROWSER} options=${options} alias=Browser 1 Capture page screenshot diff --git a/Examples/browser-management/open-close-browser.robot b/Examples/browser-management/open-close-browser.robot index 941b441..1a40278 100644 --- a/Examples/browser-management/open-close-browser.robot +++ b/Examples/browser-management/open-close-browser.robot @@ -3,28 +3,35 @@ Library PuppeteerLibrary Suite Teardown Close Puppeteer Test Teardown Close All Browser +*** Variables *** +${DEFAULT_BROWSER} chrome + + *** Test Cases *** Switch to new browser - ${HEADLESS} Get variable value ${HEADLESS} ${False} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser http://127.0.0.1:7272/basic-html-elements.html options=${options} + Open browser http://127.0.0.1:7272/basic-html-elements.html browser=${BROWSER} options=${options} Run Async Keywords - ... Wait For New Window Open AND - ... Click Element id=open-new-tab - Switch Window NEW + ... Click Element id=open-new-tab AND + ... Wait For New Window Open + Switch Window NEW Wait Until Page Contains Element id=exampleInputEmail1 Switch Window title=Basic HTML Elements Wait Until Page Contains Element id=open-new-tab Handle multiple browser + [Teardown] Capture Page Screenshot + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser http://127.0.0.1:7272/basic-html-elements.html options=${options} alias=Browser 1 + Open browser http://127.0.0.1:7272/basic-html-elements.html browser=${BROWSER} options=${options} alias=Browser 1 Run Async Keywords ... Wait For New Window Open AND ... Click Element id=open-new-tab Switch Window NEW - Open browser http://127.0.0.1:7272/basic-html-elements.html options=${options} alias=Browser 2 + Open browser http://127.0.0.1:7272/basic-html-elements.html browser=${BROWSER} options=${options} alias=Browser 2 Switch Browser Browser 1 Wait Until Page Contains Login form Switch Browser Browser 2 diff --git a/Examples/debug-mode/enable-debug-mode.robot b/Examples/debug-mode/enable-debug-mode.robot index df31d0c..6296c94 100644 --- a/Examples/debug-mode/enable-debug-mode.robot +++ b/Examples/debug-mode/enable-debug-mode.robot @@ -3,17 +3,22 @@ Library PuppeteerLibrary Test Teardown Close All Browser Suite Teardown Close Puppeteer +*** Variables *** +${DEFAULT_BROWSER} chrome +# ${DEFAULT_BROWSER} webkit +${HOME_PAGE_URL} http://127.0.0.1:7272/login-form-example.html + *** Test Cases *** Enable debug mode - Open browser to test page http://127.0.0.1:7272/login-form-example.html + Open browser to test page Input Text id=exampleInputEmail1 demo@qahive.com Input Text id=exampleInputPassword1 123456789 *** Keywords *** Open browser to test page - [Arguments] ${url} - ${HEADLESS} Get variable value ${HEADLESS} ${False} - Run Keyword If ${HEADLESS} == ${False} Enable Debug Mode + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} + Run Keyword If ${HEADLESS} == ${False} Enable Debug Mode &{options} = create dictionary headless=${HEADLESS} - Open browser ${url} options=${options} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/form-handler/dropdown-list.robot b/Examples/form-handler/dropdown-list.robot index 872d46c..7f1b29f 100644 --- a/Examples/form-handler/dropdown-list.robot +++ b/Examples/form-handler/dropdown-list.robot @@ -5,6 +5,8 @@ Test Teardown Close All Browser Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome +# ${DEFAULT_BROWSER} webkit ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html @@ -23,6 +25,7 @@ Select dropdown list by labels with xpath *** Keywords *** Open browser to test page + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/form-handler/element-properties.robot b/Examples/form-handler/element-properties.robot index cf5dfc4..2353359 100644 --- a/Examples/form-handler/element-properties.robot +++ b/Examples/form-handler/element-properties.robot @@ -5,6 +5,7 @@ Test Teardown Close All Browser Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html @@ -20,7 +21,11 @@ Element is visible and not visible Element Should Be Visible id:prop-visible Element Should Not Be Visible id:prop-hide Run Keyword And Expect Error REGEXP:Element 'id:prop-hide' is not be visible Element Should Be Visible id:prop-hide - + +Get Element Text + ${text} = Get Text id=prop-text + Should Contain ${text} Please + Element should containt text Element Should Contain id=prop-text Please ${True} @@ -35,6 +40,7 @@ Element text should not be *** Keywords *** Open browser to test page + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/form-handler/file-upload.robot b/Examples/form-handler/file-upload.robot index 7fb6877..f611249 100644 --- a/Examples/form-handler/file-upload.robot +++ b/Examples/form-handler/file-upload.robot @@ -6,6 +6,7 @@ Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html @@ -15,6 +16,7 @@ Upload file *** Keywords *** Open browser to test page + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/form-handler/form-submit.robot b/Examples/form-handler/form-submit.robot index b6654a3..65f7fd1 100644 --- a/Examples/form-handler/form-submit.robot +++ b/Examples/form-handler/form-submit.robot @@ -3,18 +3,21 @@ Library PuppeteerLibrary Test Teardown Close All Browser Suite Teardown Close Puppeteer +*** Variables *** +${DEFAULT_BROWSER} chrome + *** Test Cases *** Submit login form Open browser to test page http://127.0.0.1:7272/login-form-example.html Input Text id=exampleInputEmail1 demo@qahive.com - Input Text id=exampleInputPassword1 123456789 + Input Text xpath=//*[@id='exampleInputPassword1'] 123456789 Click Element id=exampleCheck1 Run Async Keywords ... Wait For New Window Open AND ... Click Element css=button[type="submit"] Switch Window NEW - Wait Until Page Contains Login succeeded + Wait Until Page Contains Login succeeded Submit register form Open browser to test page http://127.0.0.1:7272/register-form-example.html @@ -29,6 +32,7 @@ Submit register form *** Keywords *** Open browser to test page [Arguments] ${url} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${url} options=${options} + Open browser ${url} browser=${BROWSER} options=${options} diff --git a/Examples/form-handler/iframe.robot b/Examples/form-handler/iframe.robot index ae260dd..d55030c 100644 --- a/Examples/form-handler/iframe.robot +++ b/Examples/form-handler/iframe.robot @@ -6,6 +6,7 @@ Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html @@ -13,10 +14,16 @@ ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html Interact with iframe element Wait Until Page Contains Element id=ifrm Select Frame id=ifrm + Input Text id=exampleInputEmail1 demo@qahive.com + Input Text xpath=//*[@id='exampleInputPassword1'] 123456789 Click Element id=exampleCheck1 + Click Element xpath=//*[@id='exampleCheck1'] + Unselect Frame + Wait Until Page Contains Element id=ifrm *** Keywords *** Open browser to test page + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + &{options} = create dictionary headless=${HEADLESS} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/input/mouse.robot b/Examples/input/mouse.robot index 21c9ea7..d0e4178 100644 --- a/Examples/input/mouse.robot +++ b/Examples/input/mouse.robot @@ -2,8 +2,11 @@ Library PuppeteerLibrary Test Setup Open browser to test page Test Teardown Close All Browser +Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome +# ${DEFAULT_BROWSER} webkit ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html @@ -22,7 +25,7 @@ Mouse drag *** Keywords *** Open browser to test page + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} ${HEADLESS} Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} - + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/utilities/asyncio.robot b/Examples/utilities/asyncio.robot index efb330f..4407fae 100644 --- a/Examples/utilities/asyncio.robot +++ b/Examples/utilities/asyncio.robot @@ -1,10 +1,12 @@ -*** Settings *** +*** Settings *** Library PuppeteerLibrary Test Setup Open browser to test page Test Teardown Close All Browser Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome +# ${DEFAULT_BROWSER} webkit ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html @@ -13,7 +15,7 @@ Run Async Keywords and wait for first completed keyword ${result} = Run Async Keywords And Return First Completed ... Click Element id=non_existing_id AND ... Click Element id=get_ajax - Should Be Equal As Integers 1 ${result} + Should Be Equal As Integers 1 ${result} Run Keyword If ${result} == 0 Log first keyword completed Run Keyword If ${result} == 1 Log second keyword completed @@ -24,6 +26,7 @@ Ignore error Run Async Keywords and Return First Complete if no keyword success *** Keywords *** Open browser to test page - ${HEADLESS} Get variable value ${HEADLESS} ${False} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/utilities/execute-javascript.robot b/Examples/utilities/execute-javascript.robot index 9995ce3..4b2882b 100644 --- a/Examples/utilities/execute-javascript.robot +++ b/Examples/utilities/execute-javascript.robot @@ -1,23 +1,28 @@ *** Settings *** Library PuppeteerLibrary Test Setup Open browser to test page -Test Teardown Close Browser +Test Teardown Close All Browser +Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome +# ${DEFAULT_BROWSER} webkit ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html *** Test Cases *** Execute javascript command + [Tags] Ignore_webkit Ignore_firefox Handle Alert ACCEPT - Execute Javascript console.log('Hi five'); Execute Javascript alert('Hello world'); - +Execute javascript log + Execute Javascript console.log('Hi five'); + *** Keywords *** Open browser to test page - ${HEADLESS} Get variable value ${HEADLESS} ${False} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} - + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/utilities/generate-pdf.robot b/Examples/utilities/generate-pdf.robot index 81b3bac..def3b80 100644 --- a/Examples/utilities/generate-pdf.robot +++ b/Examples/utilities/generate-pdf.robot @@ -4,16 +4,23 @@ Test Setup Open browser to test page Test Teardown Close All Browser Suite Teardown Close Puppeteer + *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html *** Test Cases *** Generate pdf file - [Documentation] Only support on headless mode + [Tags] Ignore_webkit Ignore_firefox + [Documentation] Only support on chrome headless mode Print as pdf + *** Keywords *** Open browser to test page - &{options} = create dictionary headless=${True} - Open browser ${HOME_PAGE_URL} options=${options} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${True} + &{options} = create dictionary headless=${HEADLESS} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} + diff --git a/Examples/utilities/log-messages.robot b/Examples/utilities/log-messages.robot index a05ae38..3d2d8ad 100644 --- a/Examples/utilities/log-messages.robot +++ b/Examples/utilities/log-messages.robot @@ -1,16 +1,18 @@ *** Settings *** Library PuppeteerLibrary Test Setup Open browser to test page -Test Teardown Close Browser +Test Teardown Close All Browser +Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html *** Test Cases *** No node found when click - Run Keyword And Expect Error REGEXP:.*No node found for selector: #login_button_error Click Element id:login_button_error + Run Keyword And Expect Error REGEXP:.* Click Element id:login_button_error Test log error for sync keywords Run Keyword And Ignore Error Click Element id:login_button_error @@ -20,8 +22,10 @@ Test log error for async keywords ... Click Element id:login_button_error AND ... Click Element id:login_button_2 + *** Keywords *** Open browser to test page - ${HEADLESS} Get variable value ${HEADLESS} ${False} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${True} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/utilities/screenshot.robot b/Examples/utilities/screenshot.robot index 1c917ae..5209424 100644 --- a/Examples/utilities/screenshot.robot +++ b/Examples/utilities/screenshot.robot @@ -2,9 +2,10 @@ Library PuppeteerLibrary Test Setup Open browser to test page Test Teardown Close Browser - +Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html @@ -13,8 +14,10 @@ Capture page screenshot Capture Page Screenshot Capture Page Screenshot test-{index}.png + *** Keywords *** Open browser to test page - ${HEADLESS} Get variable value ${HEADLESS} ${False} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} diff --git a/Examples/utilities/timeout.robot b/Examples/utilities/timeout.robot index a03353f..e70c5e7 100644 --- a/Examples/utilities/timeout.robot +++ b/Examples/utilities/timeout.robot @@ -1,10 +1,12 @@ *** Settings *** Library PuppeteerLibrary -Test Setup Open test browser +Test Setup Open browser to test page Test Teardown Close Browser +Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html @@ -20,8 +22,9 @@ Timeout wait for new window open ... Click Element id:open-new-tab *** Keywords *** -Open test browser - ${HEADLESS} Get variable value ${HEADLESS} ${False} +Open browser to test page + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} - Maximize Browser Window + Open browser ${HOME_PAGE_URL} browser=${BROWSER} options=${options} + diff --git a/Examples/utilities/wait.robot b/Examples/utilities/wait.robot index 9b677eb..fbb8980 100644 --- a/Examples/utilities/wait.robot +++ b/Examples/utilities/wait.robot @@ -1,24 +1,22 @@ *** Settings *** Library PuppeteerLibrary Test Teardown Close Browser +Suite Teardown Close Puppeteer *** Variables *** +${DEFAULT_BROWSER} chrome ${HOME_PAGE_URL} http://127.0.0.1:7272/basic-html-elements.html ${LOGIN_PAGE_URL} http://127.0.0.1:7272/login-form-example.html *** Test Cases *** Wait for element - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser to test page ${HOME_PAGE_URL} Wait Until Page Contains Element id:get_ajax Run Keyword And Expect Error STARTS: TimeoutError: Wait Until Page Contains Element css:no_element timeout=5s Wait for http request - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser to test page ${HOME_PAGE_URL} ${results} = Run Async Keywords ... Wait for request url /ajax_info.json AND ... Click Element id:get_ajax @@ -27,9 +25,7 @@ Wait for http request Log ${results[0].body} Wait for http response - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser to test page ${HOME_PAGE_URL} ${results} = Run Async Keywords ... Wait for response url /ajax_info.json\\?count=3 200 name.*?p1.*?name.*?p2.*?name.*?p3 AND ... Click Element id:get_ajax @@ -38,36 +34,35 @@ Wait for http response Log ${results[0].body} Wait for navigation - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser to test page ${HOME_PAGE_URL} Run Async Keywords ... Wait For Navigation AND ... Click Element id=goto-login-page Wait for element hidden and visible - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser to test page ${HOME_PAGE_URL} Click Element id:click_and_hide Wait Until Element Is Hidden id:click_and_hide Wait for element contains text - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${LOGIN_PAGE_URL} options=${options} + Open browser to test page ${LOGIN_PAGE_URL} Wait Until Element Contains id:emailHelp We'll never share your email Wait Until Element Does Not Contains id:emailHelp We'll never share your password Wait for location contains - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${LOGIN_PAGE_URL} options=${options} + Open browser to test page ${LOGIN_PAGE_URL} Wait Until Location Contains login-form-example.html Wait for element is enabled - ${HEADLESS} Get variable value ${HEADLESS} ${False} - &{options} = create dictionary headless=${HEADLESS} - Open browser ${HOME_PAGE_URL} options=${options} + Open browser to test page ${HOME_PAGE_URL} Wait Until Element Is Enabled id=prop-enable Click Element id=prop-enable + + +*** Keywords *** +Open browser to test page + [Arguments] ${url} + ${BROWSER} = Get variable value ${BROWSER} ${DEFAULT_BROWSER} + ${HEADLESS} = Get variable value ${HEADLESS} ${False} + &{options} = create dictionary headless=${HEADLESS} + Open browser ${url} browser=${BROWSER} options=${options} diff --git a/PuppeteerLibrary/__init__.py b/PuppeteerLibrary/__init__.py index 4fb582d..c7f2e50 100644 --- a/PuppeteerLibrary/__init__.py +++ b/PuppeteerLibrary/__init__.py @@ -1,34 +1,26 @@ +from typing import List +from PuppeteerLibrary.base.ipuppeteer_library import iPuppeteerLibrary import asyncio from robot.api.deco import not_keyword from robot.api import logger -from pyppeteer.browser import Browser, BrowserContext from robot.libraries.BuiltIn import BuiltIn -from PuppeteerLibrary.custom_elements.SPage import SPage +from pyppeteer.browser import Browser +from PuppeteerLibrary.library_context.ilibrary_context import iLibraryContext +from PuppeteerLibrary.library_context.library_context_factory import LibraryContextFactory from PuppeteerLibrary.base.robotlibcore import DynamicCore from PuppeteerLibrary.keywords import ( AlertKeywords, - AlertKeywordsAsync, BrowserManagementKeywords, - BrowserManagementKeywordsAsync, DropdownKeywords, - DropdownKeywordsAsync, ElementKeywords, - ElementKeywordsAsync, FormElementKeywords, - FormElementKeywordsAsync, JavascriptKeywords, - JavascriptKeywordsAsync, MockResponseKeywords, - MockResponseKeywordsAsync, MouseEventKeywords, - MouseEventKeywordsAsync, PDFKeywords, - PDFKeywordsAsync, ScreenshotKeywords, - ScreenshotKeywordsAsync, UtilityKeywords, - WaitingKeywords, - WaitingKeywordsAsync) + WaitingKeywords) # Get the version from the _version.py versioneer file. For a git checkout, @@ -38,7 +30,7 @@ del get_versions -class PuppeteerLibrary(DynamicCore): +class PuppeteerLibrary(DynamicCore, iPuppeteerLibrary): """PuppeteerLibrary is a web testing library for Robot Framework. PuppeteerLibrary uses the pyppeteer library internally to control a web browser. @@ -79,11 +71,11 @@ class PuppeteerLibrary(DynamicCore): ROBOT_LISTENER_API_VERSION = 3 loop = None - is_load_async_keywords = False - async_libraries = [] - browser = None - contexts = {} + puppeteer_browser: iLibraryContext = None + playwright_browser: iLibraryContext = None + + # contexts = {} current_context_name = None current_page = None current_iframe = None @@ -94,6 +86,11 @@ class PuppeteerLibrary(DynamicCore): 'devtools': False } + # new context + current_libary_context: iLibraryContext = None + library_factory: LibraryContextFactory = None + library_contexts: dict = {} + def __init__(self): try: self.loop = asyncio.get_event_loop() @@ -117,105 +114,51 @@ def __init__(self): WaitingKeywords(self) ] DynamicCore.__init__(self, libraries) - - self.async_libraries = [ - AlertKeywordsAsync(self), - BrowserManagementKeywordsAsync(self), - DropdownKeywordsAsync(self), - ElementKeywordsAsync(self), - FormElementKeywordsAsync(self), - JavascriptKeywordsAsync(self), - MockResponseKeywordsAsync(self), - MouseEventKeywordsAsync(self), - PDFKeywordsAsync(self), - ScreenshotKeywordsAsync(self), - WaitingKeywordsAsync(self) - ] + self.library_factory = LibraryContextFactory() @not_keyword - def load_async_keywords(self): - if self.is_load_async_keywords is True: - return - 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 - self.clear_current_iframe() - + def get_current_library_context(self) -> iLibraryContext: + return self.current_libary_context + @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 + async def set_current_library_context(self, context_name) -> iLibraryContext: + self.current_libary_context = self.library_contexts[context_name] + return self.current_libary_context @not_keyword - def get_current_context(self) -> BrowserContext: - return self.contexts[self.current_context_name] + def get_library_context_by_name(self, alias: str) -> iLibraryContext: + return self.library_contexts[alias] @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] - self.clear_current_iframe() - return context + def get_all_library_context(self) -> List[iLibraryContext]: + return list(self.library_contexts.values()) @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 + def get_all_library_context_dict(self) -> dict: + return self.library_contexts @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 - page.__class__ = SPage - page.selected_iframe = self.current_iframe - return page - - @not_keyword - def set_current_page(self, page) -> SPage: - self.current_page = page - page.__class__ = SPage - self.clear_current_iframe() - return self.current_page - - @not_keyword - def clear_current_page(self): - self.current_page = None - self.clear_current_iframe() + def get_browser(self) -> Browser: + return self.browser @not_keyword - def set_current_iframe(self, iframe): - self.current_iframe = iframe - + def create_library_context(self, alias: str, browser_type: str) -> iLibraryContext: + library_context = self.library_factory.create(browser_type) + self.library_contexts[alias] = library_context + self.current_libary_context = library_context + return library_context + @not_keyword - def clear_current_iframe(self): - self.current_iframe = None + def remove_library_context(self, alias): + if alias not in self.library_contexts.keys(): + return + deleted_library_context = self.library_contexts[alias] + del self.library_contexts[alias] + if self.current_libary_context == deleted_library_context: + if len(self.library_contexts) > 0: + self.current_libary_context = list(self.library_contexts.values())[-1] + else: + self.current_libary_context = None @not_keyword def run_keyword(self, name, args, kwargs): diff --git a/PuppeteerLibrary/base/context.py b/PuppeteerLibrary/base/context.py index 87675a1..201ba78 100644 --- a/PuppeteerLibrary/base/context.py +++ b/PuppeteerLibrary/base/context.py @@ -1,8 +1,11 @@ + +from PuppeteerLibrary.base.ipuppeteer_library import iPuppeteerLibrary import asyncio class ContextAware(object): loop = None + ctx: iPuppeteerLibrary = None def __init__(self, ctx): """Base class exposing attributes from the common context. diff --git a/PuppeteerLibrary/base/ipuppeteer_library.py b/PuppeteerLibrary/base/ipuppeteer_library.py new file mode 100644 index 0000000..4a020c8 --- /dev/null +++ b/PuppeteerLibrary/base/ipuppeteer_library.py @@ -0,0 +1,21 @@ +from typing import List +from PuppeteerLibrary.library_context.ilibrary_context import iLibraryContext +from abc import ABC, abstractmethod + +class iPuppeteerLibrary(ABC): + + @abstractmethod + def get_current_library_context(self) -> iLibraryContext: + pass + + @abstractmethod + def get_library_context_by_name(self, alias: str) -> iLibraryContext: + pass + + @abstractmethod + def get_all_library_context(self) -> List[iLibraryContext]: + pass + + @abstractmethod + def create_library_context(self,alias: str, browser_type: str) -> iLibraryContext: + pass diff --git a/PuppeteerLibrary/custom_elements/base_page.py b/PuppeteerLibrary/custom_elements/base_page.py new file mode 100644 index 0000000..30fcb56 --- /dev/null +++ b/PuppeteerLibrary/custom_elements/base_page.py @@ -0,0 +1,75 @@ +from abc import ABC, abstractmethod +from typing import Any + +class BasePage(ABC): + + @abstractmethod + def get_page(self) -> any: + pass + + @abstractmethod + async def goto(self, url: str): + pass + + @abstractmethod + async def go_back(self): + pass + + @abstractmethod + async def reload_page(self): + pass + + @abstractmethod + async def title(self): + pass + + @abstractmethod + async def set_viewport_size(self, width, height): + pass + + ############ + # Click + ############ + @abstractmethod + async def click_with_selenium_locator(self, selenium_locator: str, options: dict = None, **kwargs: Any): + pass + + ############ + # Type + ############ + @abstractmethod + async def type_with_selenium_locator(self, selenium_locator: str, text: str, options: dict = None, **kwargs: Any): + pass + + ############ + # Wait + ############ + @abstractmethod + async def waitForSelector_with_selenium_locator(self, selenium_locator: str, timeout: float, visible=False, hidden=False): + pass + + ############################## + # Query + ############################## + @abstractmethod + async def querySelector(self, selector: str): + pass + + @abstractmethod + async def querySelectorAll_with_selenium_locator(self, selenium_locator: str): + pass + + @abstractmethod + async def querySelector_with_selenium_locator(self, selenium_locator: str): + pass + + ############################## + # iframe + ############################## + @abstractmethod + def set_current_iframe(self, iframe): + pass + + @abstractmethod + def unselect_iframe(self): + pass diff --git a/PuppeteerLibrary/ikeywords/__init__.py b/PuppeteerLibrary/ikeywords/__init__.py new file mode 100644 index 0000000..79c8a32 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/__init__.py @@ -0,0 +1,11 @@ +from .base_async_keywords import BaseAsyncKeywords +from .ialert_async import iAlertAsync +from .ibrowsermanagement_async import iBrowserManagementAsync +from .idropdown_async import iDropdownAsync +from .ielement_async import iElementAsync +from .iformelement_async import iFormElementAsync +from .ijavascript_async import iJavascriptAsync +from .imouseevent_async import iMouseEventAsync +from .iscreenshot_async import iScreenshotAsync +from .iwaiting_async import iWaitingAsync +from .ipdf_async import iPDFAsync diff --git a/PuppeteerLibrary/ikeywords/base_async_keywords.py b/PuppeteerLibrary/ikeywords/base_async_keywords.py new file mode 100644 index 0000000..a6f6634 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/base_async_keywords.py @@ -0,0 +1,23 @@ +from robot.api import logger +from robot.utils import timestr_to_secs +from PuppeteerLibrary.library_context.ilibrary_context import iLibraryContext + + +class BaseAsyncKeywords: + + def __init__(self, library_ctx: iLibraryContext): + self.library_ctx = library_ctx + + def info(self, msg, html=False): + logger.info(msg, html) + + def debug(self, msg, html=False): + logger.debug(msg, html) + + def warn(self, msg, html=False): + logger.warn(msg, html) + + def timestr_to_secs_for_default_timeout(self, timeout): + if timeout is None or timeout == '': + timeout = self.library_ctx.timeout + return timestr_to_secs(timeout) diff --git a/PuppeteerLibrary/ikeywords/ialert_async.py b/PuppeteerLibrary/ikeywords/ialert_async.py new file mode 100644 index 0000000..8577328 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/ialert_async.py @@ -0,0 +1,9 @@ +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords +from abc import ABC, abstractmethod + + +class iAlertAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def handle_alert(self, action, prompt_text=''): + pass diff --git a/PuppeteerLibrary/ikeywords/ibrowsermanagement_async.py b/PuppeteerLibrary/ikeywords/ibrowsermanagement_async.py new file mode 100644 index 0000000..2920321 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/ibrowsermanagement_async.py @@ -0,0 +1,44 @@ +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords + + +class iBrowserManagementAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def maximize_browser_window(self, width=1366, height=768): + pass + + @abstractmethod + async def go_to(self, url): + pass + + @abstractmethod + async def go_back(self, url): + pass + + @abstractmethod + async def reload_page(self): + pass + + @abstractmethod + async def get_window_count(self): + pass + + @abstractmethod + async def wait_for_new_window_open(self, timeout=None): + pass + + @abstractmethod + async def switch_window(self, locator='MAIN'): + pass + + ############################## + # iFrame + ############################## + @abstractmethod + async def select_frame(self, locator: str): + pass + + @abstractmethod + def unselect_iframe(self): + pass diff --git a/PuppeteerLibrary/ikeywords/idropdown_async.py b/PuppeteerLibrary/ikeywords/idropdown_async.py new file mode 100644 index 0000000..53ec553 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/idropdown_async.py @@ -0,0 +1,13 @@ +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords + + +class iDropdownAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def select_from_list_by_value(self, locator, values): + pass + + @abstractmethod + async def select_from_list_by_label(self, locator, labels): + pass diff --git a/PuppeteerLibrary/ikeywords/ielement_async.py b/PuppeteerLibrary/ikeywords/ielement_async.py new file mode 100644 index 0000000..7f25f96 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/ielement_async.py @@ -0,0 +1,58 @@ +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords +from abc import ABC, abstractmethod + + +class iElementAsync(BaseAsyncKeywords, ABC): + + ############################## + # Action + ############################## + @abstractmethod + async def click_element(self, locator: str): + pass + + @abstractmethod + async def upload_file(self, locator: str, file_path: str): + pass + + ############################## + # Status + ############################## + @abstractmethod + async def element_should_be_enabled(self, locator: str): + pass + + @abstractmethod + async def element_should_be_disabled(self, locator: str): + pass + + @abstractmethod + async def element_should_be_visible(self, locator:str): + pass + + @abstractmethod + async def element_should_not_be_visible(self, locator:str): + pass + + ############################## + # Property + ############################## + @abstractmethod + async def element_should_contain(self, locator: str, expected: str, ignore_case=False): + pass + + @abstractmethod + async def element_should_not_contain(self, locator: str, expected: str, ignore_case=False): + pass + + @abstractmethod + async def get_text(self, locator: str): + pass + + @abstractmethod + async def element_text_should_be(self, locator: str, expected: str, ignore_case=False): + pass + + @abstractmethod + async def element_text_should_not_be(self, locator: str, expected: str, ignore_case=False): + pass diff --git a/PuppeteerLibrary/ikeywords/iformelement_async.py b/PuppeteerLibrary/ikeywords/iformelement_async.py new file mode 100644 index 0000000..0c9c5f6 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/iformelement_async.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords + + +class iFormElementAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def input_text(self, locator: str, text: str, clear=True): + pass + + @abstractmethod + async def clear_element_text(self, locator: str): + pass + \ No newline at end of file diff --git a/PuppeteerLibrary/ikeywords/ijavascript_async.py b/PuppeteerLibrary/ikeywords/ijavascript_async.py new file mode 100644 index 0000000..e9df977 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/ijavascript_async.py @@ -0,0 +1,9 @@ +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords + + +class iJavascriptAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def execute_javascript(self, code): + pass diff --git a/PuppeteerLibrary/ikeywords/imockresponse_async.py b/PuppeteerLibrary/ikeywords/imockresponse_async.py new file mode 100644 index 0000000..d39c74a --- /dev/null +++ b/PuppeteerLibrary/ikeywords/imockresponse_async.py @@ -0,0 +1,9 @@ +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords + + +class iMockResponseAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def mock_current_page_api_response(self, url, mock_response, method='GET', body=None): + pass diff --git a/PuppeteerLibrary/ikeywords/imouseevent_async.py b/PuppeteerLibrary/ikeywords/imouseevent_async.py new file mode 100644 index 0000000..54f2cc6 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/imouseevent_async.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords + + +class iMouseEventAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def mouse_over(self, locator): + pass + + @abstractmethod + async def mouse_down(self, locator): + pass + + @abstractmethod + async def mouse_up(self): + pass + + @abstractmethod + def mouse_move(self, x, y): + pass + \ No newline at end of file diff --git a/PuppeteerLibrary/keywords/pdf_async.py b/PuppeteerLibrary/ikeywords/ipdf_async.py similarity index 58% rename from PuppeteerLibrary/keywords/pdf_async.py rename to PuppeteerLibrary/ikeywords/ipdf_async.py index dbd027f..82cc9b7 100644 --- a/PuppeteerLibrary/keywords/pdf_async.py +++ b/PuppeteerLibrary/ikeywords/ipdf_async.py @@ -1,23 +1,19 @@ import os -from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent - +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords DEFAULT_FILENAME_PAGE = 'pdf-{index}.pdf' -class PDFKeywordsAsync(LibraryComponent): +class iPDFAsync(BaseAsyncKeywords, ABC): - def __init__(self, ctx): - self.ctx = ctx + def __init__(self, library_ctx): + super().__init__(library_ctx) self.log_dir = os.curdir - @keyword - async def print_as_pdf_async(self, filename): - path = self._get_pdf_path(filename) - await self.ctx.current_page.emulateMedia('screen') - await self.ctx.current_page.pdf({'path': path}) - self.info('Print as pdf: '+path) - + @abstractmethod + async def print_as_pdf(self, filename=DEFAULT_FILENAME_PAGE): + pass + def _get_pdf_path(self, filename): directory = self.log_dir filename = filename.replace('/', os.sep) diff --git a/PuppeteerLibrary/ikeywords/iscreenshot_async.py b/PuppeteerLibrary/ikeywords/iscreenshot_async.py new file mode 100644 index 0000000..02f057e --- /dev/null +++ b/PuppeteerLibrary/ikeywords/iscreenshot_async.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords + +DEFAULT_FILENAME_PAGE = 'puppeteer-screenshot-{index}.png' + +class iScreenshotAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def capture_page_screenshot(self, path: str): + pass + + \ No newline at end of file diff --git a/PuppeteerLibrary/ikeywords/iwaiting_async.py b/PuppeteerLibrary/ikeywords/iwaiting_async.py new file mode 100644 index 0000000..8b59152 --- /dev/null +++ b/PuppeteerLibrary/ikeywords/iwaiting_async.py @@ -0,0 +1,61 @@ +from abc import ABC, abstractmethod +from PuppeteerLibrary.ikeywords.base_async_keywords import BaseAsyncKeywords + + +class iWaitingAsync(BaseAsyncKeywords, ABC): + + @abstractmethod + async def wait_for_request_url(self, url, method='GET', body=None, timeout=None): + pass + + @abstractmethod + async def wait_for_response_url(self, url, status=200, body=None, timeout=None): + pass + + @abstractmethod + async def wait_for_navigation(self, timeout=None): + pass + + @abstractmethod + async def wait_until_page_contains_element(self, locator, timeout=None): + pass + + @abstractmethod + async def wait_until_element_is_hidden(self, locator, timeout=None): + pass + + @abstractmethod + async def wait_until_element_is_visible(self, locator, timeout=None): + pass + + @abstractmethod + async def wait_until_page_contains(self, text, timeout=None): + pass + + @abstractmethod + async def wait_until_page_does_not_contains(self, text, timeout=None): + pass + + @abstractmethod + async def wait_until_element_contains(self, locator, text, timeout=None): + pass + + @abstractmethod + async def wait_until_element_does_not_contains(self, locator, text, timeout=None): + pass + + @abstractmethod + async def wait_until_location_contains(self, expected, timeout=None): + pass + + @abstractmethod + async def wait_until_location_does_not_contains(self, expected, timeout=None): + pass + + @abstractmethod + async def wait_until_element_is_enabled(self, locator, timeout=None): + pass + + @abstractmethod + async def wait_until_element_finished_animating(self, locator, timeout=None): + pass diff --git a/PuppeteerLibrary/keywords/__init__.py b/PuppeteerLibrary/keywords/__init__.py index 61037cf..3560834 100644 --- a/PuppeteerLibrary/keywords/__init__.py +++ b/PuppeteerLibrary/keywords/__init__.py @@ -1,23 +1,12 @@ from .alert import AlertKeywords -from .alert_async import AlertKeywordsAsync from .browsermanagement import BrowserManagementKeywords -from .browsermanagement_async import BrowserManagementKeywordsAsync from .dropdown import DropdownKeywords -from .dropdown_async import DropdownKeywordsAsync from .element import ElementKeywords -from .element_async import ElementKeywordsAsync from .formelement import FormElementKeywords -from .formelement_async import FormElementKeywordsAsync from .javascript import JavascriptKeywords -from .javascript_async import JavascriptKeywordsAsync from .mockresponse import MockResponseKeywords -from .mockresponse_async import MockResponseKeywordsAsync from .mouseevent import MouseEventKeywords -from .mouseevent_async import MouseEventKeywordsAsync from .pdf import PDFKeywords -from .pdf_async import PDFKeywordsAsync from .screenshot import ScreenshotKeywords -from .screenshot_async import ScreenshotKeywordsAsync from .utility import UtilityKeywords from .waiting import WaitingKeywords -from .waiting_async import WaitingKeywordsAsync diff --git a/PuppeteerLibrary/keywords/alert.py b/PuppeteerLibrary/keywords/alert.py index ebd36ac..a1263a0 100644 --- a/PuppeteerLibrary/keywords/alert.py +++ b/PuppeteerLibrary/keywords/alert.py @@ -1,13 +1,15 @@ +from PuppeteerLibrary.ikeywords.ialert_async import iAlertAsync from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.keywords.alert_async import AlertKeywordsAsync class AlertKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = AlertKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iAlertAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def handle_alert(self, action, prompt_text=''): @@ -23,5 +25,8 @@ def handle_alert(self, action, prompt_text=''): | ... Handle Alert | ACCEPT | AND | | ... Click Button | id=alert_confirm | | + Limitation: + Not support for webkit + """ - return self.loop.run_until_complete(self.async_func.handle_alert_async(action, prompt_text)) + return self.loop.run_until_complete(self.get_async_keyword_group().handle_alert(action, prompt_text)) diff --git a/PuppeteerLibrary/keywords/alert_async.py b/PuppeteerLibrary/keywords/alert_async.py deleted file mode 100644 index be1b89e..0000000 --- a/PuppeteerLibrary/keywords/alert_async.py +++ /dev/null @@ -1,17 +0,0 @@ -import asyncio - -from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent - - -class AlertKeywordsAsync(LibraryComponent): - - @keyword - async def handle_alert_async(self, action, prompt_text=''): - return self.ctx.get_current_page().on('dialog', lambda dialog: asyncio.ensure_future(self.handle_dialog(dialog, action, prompt_text))) - - async def handle_dialog(self, dialog, action, prompt_text=''): - if action == 'ACCEPT': - await dialog.accept(prompt_text) - elif action == 'DISMISS': - await dialog.dismiss() diff --git a/PuppeteerLibrary/keywords/browsermanagement.py b/PuppeteerLibrary/keywords/browsermanagement.py index 6667b47..218567b 100644 --- a/PuppeteerLibrary/keywords/browsermanagement.py +++ b/PuppeteerLibrary/keywords/browsermanagement.py @@ -1,17 +1,15 @@ -import re -import sys -from robot.utils import timestr_to_secs -from pyppeteer import launch from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.keywords.browsermanagement_async import BrowserManagementKeywordsAsync +from PuppeteerLibrary.ikeywords.ibrowsermanagement_async import iBrowserManagementAsync class BrowserManagementKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = BrowserManagementKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iBrowserManagementAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def open_browser(self, url, browser="chrome", alias=None, options=None): @@ -19,9 +17,10 @@ def open_browser(self, url, browser="chrome", alias=None, options=None): The ``browser`` argument specifies which browser to use. - | = Browser = | = Name(s) = | - | Google Chrome | chrome | - + | = Browser = | = Name(s) = | + | Google Chrome | chrome | + | Webkit (Safari engine) | webkit | + | Firefox | firefox | The ``options`` argument as a dictionary @@ -29,7 +28,7 @@ def open_browser(self, url, browser="chrome", alias=None, options=None): | headless | default True | | width | default 1366 | | height | default 768 | - + | emulate | iPhone 11 | Example: @@ -37,60 +36,34 @@ def open_browser(self, url, browser="chrome", alias=None, options=None): | `Open browser` | https://www.w3schools.com/html/html_forms.asp | options=${options} | """ - async def open_browser_async(): - if self.ctx.browser is None: - default_args = [] - default_options = { - 'slowMo': 0, - 'headless': True, - 'devtools': False, - 'width': 1366, - 'height': 768 - } - - merged_options = default_options - - if options is not None: - merged_options = {**merged_options, **options} - - if self.ctx.debug_mode is True: - merged_options = {**merged_options, **self.ctx.debug_mode_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( - slowMo=merged_options['slowMo'], - devtools=merged_options['devtools'], - 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()) + library_context = self.ctx.create_library_context(alias, browser) + self.loop.run_until_complete(library_context.start_server(options)) + self.loop.run_until_complete(library_context.create_new_page(options)) + self.loop.run_until_complete(self.get_async_keyword_group().go_to(url)) @keyword def close_browser(self, alias=None): """Closes the current browser """ - self.loop.run_until_complete(self.async_func.close_browser_async(alias)) + library_context = self.ctx.get_current_library_context() + if alias is not None: + library_context = self.ctx.get_library_context_by_name(alias) + self.loop.run_until_complete(library_context.close_browser_context()) @keyword def close_all_browser(self): """Close all browser """ - self.loop.run_until_complete(self.async_func.close_all_browser_async()) + library_contexts = self.ctx.get_all_library_context() + for library_context in library_contexts: + self.loop.run_until_complete(library_context.close_browser_context()) @keyword def close_puppeteer(self): - self.loop.run_until_complete(self.async_func.close_puppeteer_async()) + library_contexts_dict = self.ctx.get_all_library_context_dict() + for key in list(library_contexts_dict.keys()): + self.loop.run_until_complete(library_contexts_dict[key].stop_server()) + self.ctx.remove_library_context(key) @keyword def maximize_browser_window(self, width=1366, height=768): @@ -98,12 +71,7 @@ def maximize_browser_window(self, width=1366, height=768): """ self.info(('width: ' + str(width) + '\n' + 'height: ' + str(height))) - async def maximize_browser_window_async(): - await self.ctx.get_current_page().setViewport({ - 'width': width, - 'height': height - }) - self.loop.run_until_complete(maximize_browser_window_async()) + self.loop.run_until_complete(self.get_async_keyword_group().maximize_browser_window(width, height)) @keyword def get_title(self): @@ -120,48 +88,24 @@ def get_location(self): @keyword def go_back(self): """Simulate browser go back""" - async def go_back_async(): - await self.ctx.get_current_page().goBack() - self.loop.run_until_complete(go_back_async()) + self.loop.run_until_complete(self.get_async_keyword_group().go_back()) @keyword def go_to(self, url): """Navigates the current page to the ``url``""" - async def go_to_async(): - await self.ctx.get_current_page().goto(url) - self.loop.run_until_complete(go_to_async()) + self.loop.run_until_complete(self.get_async_keyword_group().go_to(url)) @keyword def reload_page(self): """Reload the current page""" - async def reload_page_async(): - await self.ctx.get_current_page().reload() - self.loop.run_until_complete(reload_page_async()) - - @keyword - def set_timeout(self, timeout): - """Sets the timeout that is used by various keywords. - The value can be given as a number that is considered to be seconds or as a human-readable string like 1 second. - The previous value is returned and can be used to restore the original value later if needed. - See the Timeout section above for more information. - - Example: - - | ${orig timeout} = | Set Timeout | 15 seconds | - | Open page that loads slowly | | | - | Set Timeout | ${orig timeout} | | - - """ - orig_timeout = self.ctx.timeout - self.ctx.timeout = timestr_to_secs(timeout) - self.info('Original timeout is ' + str(orig_timeout) + ' seconds') - return orig_timeout + self.loop.run_until_complete(self.get_async_keyword_group().reload_page()) @keyword def get_window_count(self): """ Get windows count """ - return self.loop.run_until_complete(self.async_func.get_window_count_async()) + pass + # return self.loop.run_until_complete(self.async_func.get_window_count_async()) @keyword def wait_for_new_window_open(self, timeout=None): @@ -173,7 +117,7 @@ def wait_for_new_window_open(self, timeout=None): | Run Async Keywords | Click Element | id:view_conditions | AND | | ... | `Wait For New Window Open` | | | """ - self.loop.run_until_complete(self.async_func.wait_for_new_window_open_async(timeout)) + self.loop.run_until_complete(self.get_async_keyword_group().wait_for_new_window_open(timeout)) @keyword def switch_window(self, locator='MAIN'): @@ -184,51 +128,20 @@ def switch_window(self, locator='MAIN'): - title="QAHive": window title. Page title will have have error if new tab have auto redirection - url="https://qahive.com": url support regex Example: url=.*qahive.com """ - async def switch_window_async(): - pages = await self.ctx.get_browser().pages() - if locator == 'MAIN': - page = pages[0] - await page.bringToFront() - return self.ctx.set_current_page(page) - - elif locator == 'NEW': - page = pages[-1] - await page.bringToFront() - return self.ctx.set_current_page(page) - - elif 'title=' in locator: - title = locator.replace('title=', '') - for page in pages: - page_title = await page.title() - if page_title == title: - await page.bringToFront() - return self.ctx.set_current_page(page) - self.info('Title mismatch: ' + page_title) - - elif 'url=' in locator: - url = locator.replace('url=', '') - for page in pages: - if re.match(url, page.url): - await page.bringToFront() - return self.ctx.set_current_page(page) - self.info('Url mismatch: ' + page.url) - else: - raise Exception('Sorry Switch window support only NEW, MAIN, title and url') - raise Exception('Can\'t find specify page locator.') - - return self.loop.run_until_complete(switch_window_async()) + self.loop.run_until_complete(self.get_async_keyword_group().switch_window(locator)) @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)) + return self.loop.run_until_complete(self.ctx.set_current_library_context(alias)) @keyword def enable_emulate_mode(self, emulate_name): """Emulate specific mobile or tablet The ``emulate_name`` argument specifies which emulator to use. + Only support for chrome (Puppeteer) | = Example Options = | | iPhone X | @@ -237,12 +150,12 @@ def enable_emulate_mode(self, emulate_name): More emulate_name please visit [device_descriptors.py](https://github.com/qahive/robotframework-puppeteer/tree/master/PuppeteerLibrary/utils/device_descriptors.py) """ - return self.loop.run_until_complete(self.async_func.enable_emulate_mode_async(emulate_name)) + return self.loop.run_until_complete(self.get_async_keyword_group().enable_emulate_mode_async(emulate_name)) @keyword - def select_frame(self, selenium_locator): - return self.loop.run_until_complete(self.async_func.select_frame_async(selenium_locator)) + def select_frame(self, locator): + return self.loop.run_until_complete(self.get_async_keyword_group().select_frame(locator)) @keyword def unselect_frame(self): - self.ctx.clear_current_iframe() + self.get_async_keyword_group().unselect_iframe() diff --git a/PuppeteerLibrary/keywords/browsermanagement_async.py b/PuppeteerLibrary/keywords/browsermanagement_async.py deleted file mode 100644 index 24b232b..0000000 --- a/PuppeteerLibrary/keywords/browsermanagement_async.py +++ /dev/null @@ -1,67 +0,0 @@ -import asyncio -import time -from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent -from PuppeteerLibrary.utils.device_descriptors import DEVICE_DESCRIPTORS - - -class BrowserManagementKeywordsAsync(LibraryComponent): - - @keyword - async def get_window_count_async(self): - pages = await self.ctx.get_browser().pages() - for page in pages: - # Workaround: for force pages re-cache - try: - await page.title() - except: - return -1 - return len(await self.ctx.get_browser().pages()) - - @keyword - async def wait_for_new_window_open_async(self, timeout=None): - page_len = 0 - # Workaround: - # We get length without force reset. For ensure that when we count page length. - # Page length still not update / same as before open new window - pre_page_len = len(await self.ctx.get_browser().pages()) - timeout = self.timestr_to_secs_for_default_timeout(timeout) - max_time = time.time() + timeout - while time.time() < max_time: - page_len = await self.get_window_count_async() - if page_len > pre_page_len: - return - await asyncio.sleep(0.5) - 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() - - @keyword - async def enable_emulate_mode_async(self, emulate_name): - await self.ctx.get_current_page().emulate(DEVICE_DESCRIPTORS[emulate_name]) - - @keyword - async def select_frame_async(self, selenium_locator): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) - iframe = await element.contentFrame() - self.ctx.set_current_iframe(iframe) diff --git a/PuppeteerLibrary/keywords/dropdown.py b/PuppeteerLibrary/keywords/dropdown.py index e4acf11..21050f7 100644 --- a/PuppeteerLibrary/keywords/dropdown.py +++ b/PuppeteerLibrary/keywords/dropdown.py @@ -1,18 +1,20 @@ -from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.keywords.dropdown_async import DropdownKeywordsAsync +from PuppeteerLibrary.base.librarycomponent import LibraryComponent +from PuppeteerLibrary.ikeywords.idropdown_async import iDropdownAsync class DropdownKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = DropdownKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iDropdownAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def select_from_list_by_value(self, locator, values): - return self.loop.run_until_complete(self.async_func.select_from_list_by_value_async(locator, values)) + return self.loop.run_until_complete(self.get_async_keyword_group().select_from_list_by_value(locator, values)) @keyword def select_from_list_by_label(self, locator, labels): - return self.loop.run_until_complete(self.async_func.select_from_list_by_label_async(locator, labels)) + return self.loop.run_until_complete(self.get_async_keyword_group().select_from_list_by_label(locator, labels)) diff --git a/PuppeteerLibrary/keywords/dropdown_async.py b/PuppeteerLibrary/keywords/dropdown_async.py deleted file mode 100644 index c14b11c..0000000 --- a/PuppeteerLibrary/keywords/dropdown_async.py +++ /dev/null @@ -1,32 +0,0 @@ -from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent -from PuppeteerLibrary.locators import SelectorAbstraction - - -class DropdownKeywordsAsync(LibraryComponent): - - @keyword - async def select_from_list_by_value_async(self, selenium_locator, values): - selector_value = SelectorAbstraction.get_selector(selenium_locator) - if SelectorAbstraction.is_xpath(selenium_locator): - await self.ctx.get_current_page().evaluate(''' - element = document.evaluate('{selector_value}//option[contains(@value, "{values}")]', document, null, XPathResult.ANY_TYPE, null).iterateNext(); - element.selected = true; - '''.format(selector_value=selector_value, values=values)) - else: - await self.ctx.get_current_page().select(selector_value, values) - - @keyword - async def select_from_list_by_label_async(self, selenium_locator, labels): - selector_value = SelectorAbstraction.get_selector(selenium_locator) - if SelectorAbstraction.is_xpath(selenium_locator): - await self.ctx.get_current_page().evaluate(''' - element = document.evaluate('{selector_value}//option[text()=\"{label}\"]', document, null, XPathResult.ANY_TYPE, null).iterateNext(); - element.selected = true; - '''.format(selector_value=selector_value, label=labels)) - else: - await self.ctx.get_current_page().evaluate(''' - selector_element = document.querySelector('{selector_value}'); - element = document.evaluate('//option[text()=\"{label}\"]', selector_element, null, XPathResult.ANY_TYPE, null).iterateNext(); - element.selected = true; - '''.format(selector_value=selector_value, label=labels)) diff --git a/PuppeteerLibrary/keywords/element.py b/PuppeteerLibrary/keywords/element.py index b433eff..b27ff98 100644 --- a/PuppeteerLibrary/keywords/element.py +++ b/PuppeteerLibrary/keywords/element.py @@ -1,13 +1,15 @@ -from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.keywords.element_async import ElementKeywordsAsync +from PuppeteerLibrary.base.librarycomponent import LibraryComponent +from PuppeteerLibrary.ikeywords.ielement_async import iElementAsync class ElementKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = ElementKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iElementAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def click_element(self, locator): @@ -17,7 +19,7 @@ def click_element(self, locator): | `Click Element` | id:register | """ - return self.loop.run_until_complete(self.async_func.click_element_async(locator)) + self.loop.run_until_complete(self.get_async_keyword_group().click_element(locator)) @keyword def click_link(self, locator): @@ -57,7 +59,7 @@ def get_text(self, locator): | ${text} | `Get Text` | id:username | """ - return self.loop.run_until_complete(self.async_func.get_text_async(locator)) + return self.loop.run_until_complete(self.get_async_keyword_group().get_text(locator)) @keyword def get_value(self, locator): @@ -74,59 +76,59 @@ def element_should_be_disabled(self, locator): """ Verifies that element identified by locator is disabled. """ - return self.loop.run_until_complete(self.async_func.element_should_be_disabled_async(locator)) + return self.loop.run_until_complete(self.get_async_keyword_group().element_should_be_disabled(locator)) @keyword def element_should_be_enabled(self, locator): """ Verifies that element identified by locator is enabled. """ - return self.loop.run_until_complete(self.async_func.element_should_be_enabled_async(locator)) + return self.loop.run_until_complete(self.get_async_keyword_group().element_should_be_enabled(locator)) @keyword def element_should_be_visible(self, locator): """ Verifies that element identified by locator is visible. """ - return self.loop.run_until_complete(self.async_func.element_should_be_visible_async(locator)) + return self.loop.run_until_complete(self.get_async_keyword_group().element_should_be_visible(locator)) @keyword def element_should_not_be_visible(self, locator): """ Verifies that element identified by locator is not be visible. """ - return self.loop.run_until_complete(self.async_func.element_should_not_be_visible_async(locator)) + return self.loop.run_until_complete(self.get_async_keyword_group().element_should_not_be_visible(locator)) @keyword def element_should_contain(self, locator, expected, ignore_case=False): """ Verifies that element locator contains text `expected`. """ - return self.loop.run_until_complete(self.async_func.element_should_contain_async(locator, expected, ignore_case)) + return self.loop.run_until_complete(self.get_async_keyword_group().element_should_contain(locator, expected, ignore_case)) @keyword def element_should_not_contain(self, locator, expected, ignore_case=False): """ Verifies that element locator should not contains text `expected`. """ - return self.loop.run_until_complete(self.async_func.element_should_not_contain_async(locator, expected, ignore_case)) + return self.loop.run_until_complete(self.get_async_keyword_group().element_should_not_contain(locator, expected, ignore_case)) @keyword def element_text_should_be(self, locator, expected, ignore_case=False): """ Verifies that element locator contains exact the text `expected`. """ - return self.loop.run_until_complete(self.async_func.element_text_should_be_async(locator, expected, ignore_case)) + return self.loop.run_until_complete(self.get_async_keyword_group().element_text_should_be(locator, expected, ignore_case)) @keyword def element_text_should_not_be(self, locator, expected, ignore_case=False): """ Verifies that element locator not contains exact the text `expected`. """ - return self.loop.run_until_complete(self.async_func.element_text_should_not_be_async(locator, expected, ignore_case)) + return self.loop.run_until_complete(self.get_async_keyword_group().element_text_should_not_be(locator, expected, ignore_case)) @keyword def upload_file(self, locator, file_path): """ Upload file """ - return self.loop.run_until_complete(self.async_func.upload_file_async(locator, file_path)) + return self.loop.run_until_complete(self.get_async_keyword_group().upload_file(locator, file_path)) diff --git a/PuppeteerLibrary/keywords/element_async.py b/PuppeteerLibrary/keywords/element_async.py deleted file mode 100644 index ec3b333..0000000 --- a/PuppeteerLibrary/keywords/element_async.py +++ /dev/null @@ -1,103 +0,0 @@ -from robot.libraries.BuiltIn import BuiltIn - -from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent - - -class ElementKeywordsAsync(LibraryComponent): - - @keyword - async def click_element_async(self, selenium_locator): - return await self.ctx.get_current_page().click_with_selenium_locator(selenium_locator) - - @keyword - async def click_link_async(self, selenium_locator): - elements = await self.ctx.get_current_page().querySelectorAll_with_selenium_locator(selenium_locator) - for element in elements: - tag_name = (await (await element.getProperty('tagName')).jsonValue()).lower() - if tag_name == 'a': - return await element.click() - raise Exception('Not found link with specific locator ' + selenium_locator) - - @keyword - async def click_button_async(self, selenium_locator): - elements = await self.ctx.get_current_page().querySelectorAll_with_selenium_locator(selenium_locator) - for element in elements: - tag_name = (await (await element.getProperty('tagName')).jsonValue()).lower() - if tag_name == 'button': - return await element.click() - raise Exception('Not found button with specific locator ' + selenium_locator) - - @keyword - async def click_image_async(self, selenium_locator): - elements = await self.ctx.get_current_page().querySelectorAll_with_selenium_locator(selenium_locator) - for element in elements: - tag_name = (await (await element.getProperty('tagName')).jsonValue()).lower() - if tag_name == 'img': - return element.click() - raise Exception('Not found button with specific locator ' + selenium_locator) - - @keyword - async def get_text_async(self, selenium_locator): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) - return (await (await element.getProperty('textContent')).jsonValue()) - - @keyword - async def get_value_async(self, selenium_locator): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) - return (await (await element.getProperty('value')).jsonValue()) - - @keyword - async def element_should_be_disabled_async(self, selenium_locator): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) - is_disabled = await (await element.getProperty('disabled')).jsonValue() - if not is_disabled: - raise AssertionError("Element '%s' is enabled. " % selenium_locator) - return element - - @keyword - async def element_should_be_enabled_async(self, selenium_locator): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) - is_disabled = await (await element.getProperty('disabled')).jsonValue() - if is_disabled: - raise AssertionError("Element '%s' is disabled. " % selenium_locator) - return element - - @keyword - async def element_should_be_visible_async(self, selenium_locator): - try: - return await self.ctx.get_current_page().waitForSelector_with_selenium_locator(selenium_locator, 0.1, visible=True, hidden=False) - except: - raise AssertionError("Element '%s' is not be visible. " % selenium_locator) - - @keyword - async def element_should_not_be_visible_async(self, selenium_locator): - try: - return await self.ctx.get_current_page().waitForSelector_with_selenium_locator(selenium_locator, 0.1, visible=False, hidden=True) - except: - raise AssertionError("Element '%s' is visible. " % selenium_locator) - - @keyword - async def element_should_contain_async(self, selenium_locator, expected, ignore_case=False): - text = await self.get_text_async(selenium_locator) - return BuiltIn().should_contain(text, expected, ignore_case=ignore_case) - - @keyword - async def element_should_not_contain_async(self, selenium_locator, expected, ignore_case=False): - text = await self.get_text_async(selenium_locator) - return BuiltIn().should_not_contain(text, expected, ignore_case=ignore_case) - - @keyword - async def element_text_should_be_async(self, selenium_locator, expected, ignore_case=False): - text = await self.get_text_async(selenium_locator) - return BuiltIn().should_be_equal_as_strings(text, expected, ignore_case=ignore_case) - - @keyword - async def element_text_should_not_be_async(self, selenium_locator, expected, ignore_case=False): - text = await self.get_text_async(selenium_locator) - return BuiltIn().should_not_be_equal_as_strings(text, expected, ignore_case=ignore_case) - - @keyword - async def upload_file_async(self, selenium_locator, file_path): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) - await element.uploadFile(file_path) diff --git a/PuppeteerLibrary/keywords/formelement.py b/PuppeteerLibrary/keywords/formelement.py index 37b96b9..7b4788e 100644 --- a/PuppeteerLibrary/keywords/formelement.py +++ b/PuppeteerLibrary/keywords/formelement.py @@ -1,13 +1,15 @@ -from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.keywords.formelement_async import FormElementKeywordsAsync +from PuppeteerLibrary.base.librarycomponent import LibraryComponent +from PuppeteerLibrary.ikeywords.iformelement_async import iFormElementAsync class FormElementKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = FormElementKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iFormElementAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def input_text(self, locator, text, clear=True): @@ -21,7 +23,7 @@ def input_text(self, locator, text, clear=True): | `Input Text` | id:username | john | True | """ - self.loop.run_until_complete(self.async_func.input_text_async(locator, text, clear)) + self.loop.run_until_complete(self.get_async_keyword_group().input_text(locator, text, clear)) @keyword def clear_element_text(self, locator): @@ -31,5 +33,5 @@ def clear_element_text(self, locator): | `Clear Element Text` | id:name | """ - self.loop.run_until_complete(self.async_func.clear_element_text_async(locator)) + self.loop.run_until_complete(self.get_async_keyword_group().clear_element_text(locator)) diff --git a/PuppeteerLibrary/keywords/formelement_async.py b/PuppeteerLibrary/keywords/formelement_async.py deleted file mode 100644 index 2441ffa..0000000 --- a/PuppeteerLibrary/keywords/formelement_async.py +++ /dev/null @@ -1,21 +0,0 @@ -from robot.api.deco import not_keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent -from PuppeteerLibrary.base.robotlibcore import keyword - - -class FormElementKeywordsAsync(LibraryComponent): - - @keyword - async def input_text_async(self, selenium_locator, text, clear=True): - if clear: - await self._clear_input_text(selenium_locator) - await self.ctx.get_current_page().type_with_selenium_locator(selenium_locator, text) - - @keyword - async def clear_element_text_async(self, selenium_locator): - await self._clear_input_text(selenium_locator) - - @not_keyword - async def _clear_input_text(self, selenium_locator): - await self.ctx.get_current_page().click_with_selenium_locator(selenium_locator, {'clickCount': 3}) - await self.ctx.get_current_page().keyboard.press('Backspace') diff --git a/PuppeteerLibrary/keywords/javascript.py b/PuppeteerLibrary/keywords/javascript.py index 0b16aaa..74afe19 100644 --- a/PuppeteerLibrary/keywords/javascript.py +++ b/PuppeteerLibrary/keywords/javascript.py @@ -1,13 +1,15 @@ from PuppeteerLibrary.base.robotlibcore import keyword from PuppeteerLibrary.base.librarycomponent import LibraryComponent -from PuppeteerLibrary.keywords.javascript_async import JavascriptKeywordsAsync +from PuppeteerLibrary.ikeywords.ijavascript_async import iJavascriptAsync class JavascriptKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = JavascriptKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iJavascriptAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def execute_javascript(self, code): @@ -22,4 +24,4 @@ def execute_javascript(self, code): | `Execute Javascript` | console.log('Hi 5'); | """ - return self.loop.run_until_complete(self.async_func.execute_javascript_async(code)) + return self.loop.run_until_complete(self.get_async_keyword_group().execute_javascript(code)) diff --git a/PuppeteerLibrary/keywords/javascript_async.py b/PuppeteerLibrary/keywords/javascript_async.py deleted file mode 100644 index c3b75c6..0000000 --- a/PuppeteerLibrary/keywords/javascript_async.py +++ /dev/null @@ -1,12 +0,0 @@ -from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent - - -class JavascriptKeywordsAsync(LibraryComponent): - - def __init__(self, ctx): - self.ctx = ctx - - @keyword - async def execute_javascript_async(self, code): - return await self.ctx.get_current_page().evaluate(code) diff --git a/PuppeteerLibrary/keywords/mockresponse.py b/PuppeteerLibrary/keywords/mockresponse.py index d95dc72..d1a1ee0 100644 --- a/PuppeteerLibrary/keywords/mockresponse.py +++ b/PuppeteerLibrary/keywords/mockresponse.py @@ -1,13 +1,15 @@ +from PuppeteerLibrary.ikeywords.imockresponse_async import iMockResponseAsync from PuppeteerLibrary.base.robotlibcore import keyword from PuppeteerLibrary.base.librarycomponent import LibraryComponent -from PuppeteerLibrary.keywords.mockresponse_async import MockResponseKeywordsAsync class MockResponseKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = MockResponseKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iMockResponseAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def mock_current_page_api_response(self, url, mock_response, method='GET', body=None): @@ -44,4 +46,4 @@ def mock_current_page_api_response(self, url, mock_response, method='GET', body= | Mock Current Page Api Response | /ajax_info.json\\?count=3 | ${response} | """ - return self.loop.run_until_complete(self.async_func.mock_current_page_api_response_async(url, mock_response, method, body)) + return self.loop.run_until_complete(self.get_async_keyword_group().mock_current_page_api_response(url, mock_response, method, body)) diff --git a/PuppeteerLibrary/keywords/mouseevent.py b/PuppeteerLibrary/keywords/mouseevent.py index 6e16b8a..e740af1 100644 --- a/PuppeteerLibrary/keywords/mouseevent.py +++ b/PuppeteerLibrary/keywords/mouseevent.py @@ -1,34 +1,36 @@ +from PuppeteerLibrary.ikeywords.imouseevent_async import iMouseEventAsync from PuppeteerLibrary.base.robotlibcore import keyword from PuppeteerLibrary.base.librarycomponent import LibraryComponent -from PuppeteerLibrary.keywords.mouseevent_async import MouseEventKeywordsAsync class MouseEventKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = MouseEventKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iMouseEventAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def mouse_over(self, locator): """Mouse over the element. """ - return self.loop.run_until_complete(self.async_func.mouse_over_async(locator)) + return self.loop.run_until_complete(self.get_async_keyword_group().mouse_over(locator)) @keyword def mouse_down(self, locator): """Mouse down on the element. """ - return self.loop.run_until_complete(self.async_func.mouse_down_async(locator)) + return self.loop.run_until_complete(self.get_async_keyword_group().mouse_down(locator)) @keyword def mouse_up(self): """Mouse up. """ - return self.loop.run_until_complete(self.async_func.mouse_up_async()) + return self.loop.run_until_complete(self.get_async_keyword_group().mouse_up()) @keyword def mouse_move(self, x, y): """Move mouse to position x, y. """ - return self.loop.run_until_complete(self.async_func.mouse_move_async(x, y)) + return self.loop.run_until_complete(self.get_async_keyword_group().mouse_move(x, y)) diff --git a/PuppeteerLibrary/keywords/mouseevent_async.py b/PuppeteerLibrary/keywords/mouseevent_async.py deleted file mode 100644 index 9183337..0000000 --- a/PuppeteerLibrary/keywords/mouseevent_async.py +++ /dev/null @@ -1,30 +0,0 @@ -from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent - - -class MouseEventKeywordsAsync(LibraryComponent): - - def __init__(self, ctx): - self.ctx = ctx - - @keyword - async def mouse_over_async(self, selenium_locator): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) - await element.hover() - - @keyword - async def mouse_down_async(self, selenium_locator): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) - bounding_box = await element.boundingBox() - await self.ctx.get_current_page().mouse.move( - bounding_box['x'] + bounding_box['width'] / 2, - bounding_box['y'] + bounding_box['height'] / 2) - await self.ctx.get_current_page().mouse.down() - - @keyword - async def mouse_up_async(self): - await self.ctx.get_current_page().mouse.up() - - @keyword - async def mouse_move_async(self, x, y): - await self.ctx.get_current_page().mouse.move(int(x), int(y)) diff --git a/PuppeteerLibrary/keywords/pdf.py b/PuppeteerLibrary/keywords/pdf.py index 34ebb9a..bab4507 100644 --- a/PuppeteerLibrary/keywords/pdf.py +++ b/PuppeteerLibrary/keywords/pdf.py @@ -1,14 +1,15 @@ +from PuppeteerLibrary.ikeywords.ipdf_async import iPDFAsync, DEFAULT_FILENAME_PAGE from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.keywords.pdf_async import PDFKeywordsAsync, DEFAULT_FILENAME_PAGE - class PDFKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = PDFKeywordsAsync(self.ctx) + def get_async_keyword_group(self) -> iPDFAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) + @keyword def print_as_pdf(self, filename=DEFAULT_FILENAME_PAGE): """ @@ -25,4 +26,4 @@ def print_as_pdf(self, filename=DEFAULT_FILENAME_PAGE): | Print as PDF | custom-pdf-{index}.pdf | | """ - return self.loop.run_until_complete(self.async_func.print_as_pdf_async(filename)) + return self.loop.run_until_complete(self.get_async_keyword_group().print_as_pdf(filename)) diff --git a/PuppeteerLibrary/keywords/screenshot.py b/PuppeteerLibrary/keywords/screenshot.py index b92e3ad..94257a3 100644 --- a/PuppeteerLibrary/keywords/screenshot.py +++ b/PuppeteerLibrary/keywords/screenshot.py @@ -1,13 +1,18 @@ +import os +from robot.utils import get_link_path from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.keywords.screenshot_async import ScreenshotKeywordsAsync, DEFAULT_FILENAME_PAGE +from PuppeteerLibrary.ikeywords.iscreenshot_async import DEFAULT_FILENAME_PAGE, iScreenshotAsync class ScreenshotKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = ScreenshotKeywordsAsync(self.ctx) + self.log_dir = os.curdir + + def get_async_keyword_group(self) -> iScreenshotAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def capture_page_screenshot(self, filename=DEFAULT_FILENAME_PAGE): @@ -23,4 +28,35 @@ def capture_page_screenshot(self, filename=DEFAULT_FILENAME_PAGE): | Capture page screenshot | custom-{index}.png | """ - return self.loop.run_until_complete(self.async_func.capture_page_screenshot_async(filename)) + path = self._get_screenshot_path(filename) + self.loop.run_until_complete(self.get_async_keyword_group().capture_page_screenshot(path)) + self._embed_to_log_as_file(path, 800) + + def _get_screenshot_path(self, filename): + directory = self.log_dir + filename = filename.replace('/', os.sep) + index = 0 + while True: + index += 1 + formatted = self._format_path(filename, index) + path = os.path.join(directory, formatted) + if formatted == filename or not os.path.exists(path): + return path + + def _format_path(self, file_path, index): + return file_path.format_map(_SafeFormatter(index=index)) + + def _embed_to_log_as_file(self, path, width): + """ + Image is shown on its own row and thus previous row is closed on purpose. + Depending on Robot's log structure is a bit risky. + + """ + self.info('