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('' + '' + .format(src=get_link_path(path, self.log_dir), width=width), html=True) + +class _SafeFormatter(dict): + + def __missing__(self, key): + return '{%s}' % key diff --git a/PuppeteerLibrary/keywords/screenshot_async.py b/PuppeteerLibrary/keywords/screenshot_async.py deleted file mode 100644 index d097e67..0000000 --- a/PuppeteerLibrary/keywords/screenshot_async.py +++ /dev/null @@ -1,44 +0,0 @@ -import os -from robot.utils import get_link_path -from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent - -DEFAULT_FILENAME_PAGE = 'puppeteer-screenshot-{index}.png' - -class ScreenshotKeywordsAsync(LibraryComponent): - - def __init__(self, ctx): - self.ctx = ctx - self.log_dir = os.curdir - - @keyword - async def capture_page_screenshot_async(self, filename=DEFAULT_FILENAME_PAGE): - path = self._get_screenshot_path(filename) - await self.ctx.current_page.screenshot({'path': 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('' - '' - .format(src=get_link_path(path, self.log_dir), width=width), html=True) - -class _SafeFormatter(dict): - - def __missing__(self, key): - return '{%s}' % key diff --git a/PuppeteerLibrary/keywords/utility.py b/PuppeteerLibrary/keywords/utility.py index 980f2a9..cef1e11 100644 --- a/PuppeteerLibrary/keywords/utility.py +++ b/PuppeteerLibrary/keywords/utility.py @@ -1,11 +1,36 @@ import asyncio +from robot.utils import timestr_to_secs from robot.libraries.BuiltIn import _RunKeyword +from PuppeteerLibrary.keywords.browsermanagement import BrowserManagementKeywords +from PuppeteerLibrary.playwright.async_keywords.playwright_browsermanagement import PlaywrightBrowserManagement from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword class UtilityKeywords(LibraryComponent): + def __init__(self, ctx): + super().__init__(ctx) + + @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.get_current_library_context().timeout = timestr_to_secs(timeout) + self.info('Original timeout is ' + str(orig_timeout) + ' seconds') + return orig_timeout + @keyword def run_async_keywords_and_return_first_completed(self, *keywords): """Executes all the given keywords in a asynchronous and wait until first keyword is completed @@ -16,20 +41,16 @@ def run_async_keywords_and_return_first_completed(self, *keywords): | `Run Async Keywords And Return First Completed` | Wait for response url | ${HOME_PAGE_URL}/login.html | AND | | ... | Wait for response url | ${HOME_PAGE_URL}/home.html | | """ - self.ctx.load_async_keywords() run_keyword = _RunKeyword() return self.loop.run_until_complete( self._run_async_keywords_first_completed(run_keyword._split_run_keywords(list(keywords))) ) - async def _wrapped_async_keyword_return_index(self, index, future): - await future - return index - async def _run_async_keywords_first_completed(self, iterable): org_statements = [] index = 0 for kw, args in iterable: - kw_name = kw.lower().replace(' ', '_') + '_async' - org_statements.append(self._wrapped_async_keyword_return_index(index, self.ctx.keywords[kw_name](*args))) + kw_name = kw.lower().replace(' ', '_') + async_keywords = self.ctx.keywords[kw_name].__self__.get_async_keyword_group() + org_statements.append(self._wrapped_async_keyword_return_index(index, getattr(async_keywords, kw_name)(*args) )) index += 1 statements = org_statements error_stack_trace = '' @@ -49,7 +70,11 @@ async def _run_async_keywords_first_completed(self, iterable): continue if len(pending) == 0: raise Exception("All async keywords failed \r\n"+ error_stack_trace) - + + async def _wrapped_async_keyword_return_index(self, index, future): + await future + return index + @keyword def run_async_keywords(self, *keywords): """Executes all the given keywords in a asynchronous and wait until all keyword is completed @@ -62,14 +87,24 @@ def run_async_keywords(self, *keywords): | ... | Wait for response url | ${HOME_PAGE_URL}/home.html | | """ - self.ctx.load_async_keywords() run_keyword = _RunKeyword() - return self.loop.run_until_complete( self._run_async_keywords(run_keyword._split_run_keywords(list(keywords))) ) - + return self.loop.run_until_complete( self._new_run_async_keywords(run_keyword._split_run_keywords(list(keywords))) ) + + async def _new_run_async_keywords(self, iterable): + statements = [] + for kw, args in iterable: + kw_name = kw.lower().replace(' ', '_') + async_keywords = self.ctx.keywords[kw_name].__self__.get_async_keyword_group() + statements.append(getattr(async_keywords, kw_name)(*args)) + try: + return await asyncio.gather(*statements) + except Exception as err: + raise Exception(err) + async def _run_async_keywords(self, iterable): statements = [] for kw, args in iterable: - kw_name = kw.lower().replace(' ', '_') + '_async' + kw_name = kw.lower() statements.append(self.ctx.keywords[kw_name](*args)) try: return await asyncio.gather(*statements) diff --git a/PuppeteerLibrary/keywords/waiting.py b/PuppeteerLibrary/keywords/waiting.py index a79362a..ddcc951 100644 --- a/PuppeteerLibrary/keywords/waiting.py +++ b/PuppeteerLibrary/keywords/waiting.py @@ -1,13 +1,15 @@ -from PuppeteerLibrary.keywords.waiting_async import WaitingKeywordsAsync -from PuppeteerLibrary.base.librarycomponent import LibraryComponent from PuppeteerLibrary.base.robotlibcore import keyword +from PuppeteerLibrary.ikeywords.iwaiting_async import iWaitingAsync +from PuppeteerLibrary.base.librarycomponent import LibraryComponent class WaitingKeywords(LibraryComponent): def __init__(self, ctx): super().__init__(ctx) - self.async_func = WaitingKeywordsAsync(self.ctx) + + def get_async_keyword_group(self) -> iWaitingAsync: + return self.ctx.get_current_library_context().get_async_keyword_group(type(self).__name__) @keyword def wait_for_request_url(self, url, method='GET', body=None, timeout=None): @@ -43,7 +45,7 @@ def wait_for_request_url(self, url, method='GET', body=None, timeout=None): | ... | `Wait For Request Url` | ${URL_API}/login | POST | username=demo | """ - return self.loop.run_until_complete(self.async_func.wait_for_request_url_async(url, method, body, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_for_request_url_async(url, method, body, timeout)) @keyword def wait_for_response_url(self, url, status=200, body=None, timeout=None): @@ -73,10 +75,10 @@ def wait_for_response_url(self, url, status=200, body=None, timeout=None): | ... | `Wait For Response Url` | ${URL_API}/login | 200 | username=demo | """ - return self.loop.run_until_complete(self.async_func.wait_for_response_url_async(url, status, body, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_for_response_url(url, status, body, timeout)) @keyword - def wait_for_navigation(self): + def wait_for_navigation(self, timeout=None): """ Waits until web page navigates to new url or reloads. @@ -88,7 +90,7 @@ def wait_for_navigation(self): | Run Async Keywords | Click Element | id:login_button | AND | | ... | `Wait For Navigation` | | | """ - return self.loop.run_until_complete(self.async_func.wait_for_navigation_async()) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_for_navigation(timeout)) @keyword def wait_until_page_contains_element(self, locator, timeout=None): @@ -100,7 +102,7 @@ def wait_until_page_contains_element(self, locator, timeout=None): | Open browser | ${HOME_PAGE_URL} | options=${options} | | `Wait Until Page Contains Element` | id:username | | """ - return self.loop.run_until_complete(self.async_func.wait_until_page_contains_element_async(locator, timeout)) + self.loop.run_until_complete(self.get_async_keyword_group().wait_until_page_contains_element(locator, timeout)) @keyword def wait_until_element_is_hidden(self, locator, timeout=None): @@ -113,7 +115,7 @@ def wait_until_element_is_hidden(self, locator, timeout=None): | ... | Wait For Navigation | | | | `Wait Until Element Is Hidden` | id:login_button | | | """ - return self.loop.run_until_complete(self.async_func.wait_until_element_is_hidden_async(locator, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_element_is_hidden(locator, timeout)) @keyword def wait_until_element_is_visible(self, locator, timeout=None): @@ -126,7 +128,7 @@ def wait_until_element_is_visible(self, locator, timeout=None): | ... | Wait For Navigation | | | | `Wait Until Element Is Visible` | id:welcome | | | """ - return self.loop.run_until_complete(self.async_func.wait_until_element_is_visible_async(locator, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_element_is_visible(locator, timeout)) @keyword def wait_until_page_contains(self, text, timeout=None): @@ -139,7 +141,8 @@ def wait_until_page_contains(self, text, timeout=None): | ... | Wait For Navigation | | | | `Wait Until Page Contains` | Invalid user name or password | | | """ - return self.loop.run_until_complete(self.async_func.wait_until_page_contains_async(text, timeout)) + self.loop.run_until_complete(self.get_async_keyword_group().wait_until_page_contains(text, timeout)) + # return self.loop.run_until_complete(self.async_func.wait_until_page_contains_async(text, timeout)) @keyword def wait_until_page_does_not_contains(self, text, timeout=None): @@ -152,7 +155,7 @@ def wait_until_page_does_not_contains(self, text, timeout=None): | ... | Wait For Navigation | | | | `Wait Until Page Does Not Contains` | Please input your user name | | | """ - return self.loop.run_until_complete(self.async_func.wait_until_page_does_not_contains_async(text, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_page_does_not_contains(text, timeout)) @keyword def wait_until_element_contains(self, locator, text, timeout=None): @@ -164,7 +167,7 @@ def wait_until_element_contains(self, locator, text, timeout=None): | Open browser | ${HOME_PAGE_URL} | options=${options} | | `Wait Until Element Contains` | css:#container p | Please input your user name | """ - return self.loop.run_until_complete(self.async_func.wait_until_element_contains_async(locator, text, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_element_contains(locator, text, timeout)) @keyword def wait_until_element_does_not_contains(self, locator, text, timeout=None): @@ -177,7 +180,7 @@ def wait_until_element_does_not_contains(self, locator, text, timeout=None): | ... | Wait For Navigation | | | | `Wait Until Element Does Not Contains` | css:#container p | Please input your user name | | """ - return self.loop.run_until_complete(self.async_func.wait_until_element_does_not_contains_async(locator, text, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_element_does_not_contains(locator, text, timeout)) @keyword def wait_until_location_contains(self, expected, timeout=None): @@ -186,7 +189,7 @@ def wait_until_location_contains(self, expected, timeout=None): The `expected` argument contains the expected value in url. """ - return self.loop.run_until_complete(self.async_func.wait_until_location_contains_async(expected, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_location_contains(expected, timeout)) @keyword def wait_until_location_does_not_contains(self, expected, timeout=None): @@ -195,7 +198,7 @@ def wait_until_location_does_not_contains(self, expected, timeout=None): The `expected` argument contains the expected value must not in url. """ - return self.loop.run_until_complete(self.async_func.wait_until_location_does_not_contains_async(expected, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_location_does_not_contains(expected, timeout)) @keyword def wait_until_element_is_enabled(self, selenium_locator, timeout=None): @@ -203,8 +206,7 @@ def wait_until_element_is_enabled(self, selenium_locator, timeout=None): Waits until the specific element is Enabled. """ - return self.loop.run_until_complete( - self.async_func.wait_until_element_is_enabled_async(selenium_locator, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_element_is_enabled(selenium_locator, timeout)) @keyword def wait_until_element_finished_animating(self, selenium_locator, timeout=None): @@ -213,5 +215,4 @@ def wait_until_element_finished_animating(self, selenium_locator, timeout=None): Check by check element position. """ - return self.loop.run_until_complete( - self.async_func.wait_until_element_finished_animating_async(selenium_locator, timeout)) + return self.loop.run_until_complete(self.get_async_keyword_group().wait_until_element_finished_animating(selenium_locator, timeout)) diff --git a/PuppeteerLibrary/library_context/__init__.py b/PuppeteerLibrary/library_context/__init__.py new file mode 100644 index 0000000..d9f802f --- /dev/null +++ b/PuppeteerLibrary/library_context/__init__.py @@ -0,0 +1 @@ +from .ilibrary_context import iLibraryContext \ No newline at end of file diff --git a/PuppeteerLibrary/library_context/ilibrary_context.py b/PuppeteerLibrary/library_context/ilibrary_context.py new file mode 100644 index 0000000..bdab19c --- /dev/null +++ b/PuppeteerLibrary/library_context/ilibrary_context.py @@ -0,0 +1,51 @@ +from PuppeteerLibrary.custom_elements.base_page import BasePage +from abc import ABC, abstractmethod + + +class iLibraryContext(ABC): + + browser_type: str = None + timeout: int = 30 + + def __init__(self, browser_type: str): + self.browser_type = browser_type + + @abstractmethod + async def start_server(self, options: dict=None): + pass + + @abstractmethod + async def stop_server(self): + pass + + @abstractmethod + def is_server_started(self) -> bool: + pass + + @abstractmethod + async def create_new_page(self, options: dict=None) -> BasePage: + pass + + @abstractmethod + def get_current_page(self) -> BasePage: + pass + + @abstractmethod + def set_current_page(self, page: any) -> BasePage: + pass + + @abstractmethod + async def get_all_pages(self): + pass + + @abstractmethod + def get_browser_context(self): + pass + + @abstractmethod + async def close_browser_context(self): + pass + + @abstractmethod + def get_async_keyword_group(self, keyword_group_name: str): + pass diff --git a/PuppeteerLibrary/library_context/library_context_factory.py b/PuppeteerLibrary/library_context/library_context_factory.py new file mode 100644 index 0000000..c812910 --- /dev/null +++ b/PuppeteerLibrary/library_context/library_context_factory.py @@ -0,0 +1,21 @@ +from .ilibrary_context import iLibraryContext +from PuppeteerLibrary.puppeteer.puppeteer_context import PuppeteerContext +from PuppeteerLibrary.playwright.playwright_context import PlaywrightContext + +class LibraryContextFactory: + + def create(self, browser_type: str) -> iLibraryContext: + """Return iLibraryContext based on specific browser_type + + Create context based on browser type (chrome, safari, webkit) + """ + if browser_type.lower() == 'chrome': + return PuppeteerContext(browser_type) + elif browser_type.lower() == 'safari': + return PlaywrightContext(browser_type) + elif browser_type.lower() == 'webkit': + return PlaywrightContext(browser_type) + elif browser_type.lower() == 'firefox': + return PlaywrightContext(browser_type) + else: + raise Exception('Sorry, not supported for library type '+str(browser_type)+'.') diff --git a/PuppeteerLibrary/playwright/__init__.py b/PuppeteerLibrary/playwright/__init__.py new file mode 100644 index 0000000..3294d71 --- /dev/null +++ b/PuppeteerLibrary/playwright/__init__.py @@ -0,0 +1 @@ +# Just for notify python that this is library folder for searching for diff --git a/PuppeteerLibrary/playwright/async_keywords/__init__.py b/PuppeteerLibrary/playwright/async_keywords/__init__.py new file mode 100644 index 0000000..31c3eb1 --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/__init__.py @@ -0,0 +1 @@ +from .playwright_browsermanagement import PlaywrightBrowserManagement \ No newline at end of file diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_alert.py b/PuppeteerLibrary/playwright/async_keywords/playwright_alert.py new file mode 100644 index 0000000..b3be74f --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_alert.py @@ -0,0 +1,11 @@ +import asyncio +from PuppeteerLibrary.ikeywords.ialert_async import iAlertAsync + + +class PlaywrightAlert(iAlertAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def handle_alert(self, action, prompt_text=''): + pass diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_browsermanagement.py b/PuppeteerLibrary/playwright/async_keywords/playwright_browsermanagement.py new file mode 100644 index 0000000..9a7858d --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_browsermanagement.py @@ -0,0 +1,102 @@ +import asyncio +import re +import time +from PuppeteerLibrary.utils.device_descriptors import DEVICE_DESCRIPTORS +from PuppeteerLibrary.ikeywords.ibrowsermanagement_async import iBrowserManagementAsync + + +class PlaywrightBrowserManagement(iBrowserManagementAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def maximize_browser_window(self, width=1366, height=768): + return await self.library_ctx.get_current_page().set_viewport_size(width, height) + + async def go_to(self, url): + return await self.library_ctx.get_current_page().goto(url) + + async def go_back(self): + return await self.library_ctx.get_current_page().go_back() + + async def reload_page(self): + return await self.library_ctx.get_current_page().reload_page() + + async def get_window_count(self): + pages = await self.library_ctx.get_all_pages() + for page in pages: + # Workaround: for force pages re-cache + try: + await page.title() + except: + return -1 + return len(await self.library_ctx.get_all_pages()) + ''' + pages = await self.library_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.library_ctx.get_browser().pages()) + ''' + + async def wait_for_new_window_open(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.library_ctx.get_all_pages()) + timeout = self.timestr_to_secs_for_default_timeout(timeout) + max_time = time.time() + timeout + while time.time() < max_time: + page_len = len(await self.library_ctx.get_all_pages()) + 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)) + + async def switch_window(self, locator='MAIN'): + pages = await self.library_ctx.get_all_pages() + if locator == 'MAIN': + page = pages[0] + await page.bringToFront() + return self.library_ctx.set_current_page(page) + + elif locator == 'NEW': + page = pages[-1] + await page.bringToFront() + return self.library_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.library_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.library_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.') + + ############################## + # iFrame + ############################## + async def select_frame(self, locator: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + iframe = await element.contentFrame() + self.library_ctx.get_current_page().set_current_iframe(iframe) + + def unselect_iframe(self): + self.library_ctx.get_current_page().unselect_iframe() + diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_dropdown.py b/PuppeteerLibrary/playwright/async_keywords/playwright_dropdown.py new file mode 100644 index 0000000..ddeaa14 --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_dropdown.py @@ -0,0 +1,32 @@ +from PuppeteerLibrary.ikeywords.idropdown_async import iDropdownAsync +from PuppeteerLibrary.locators.SelectorAbstraction import SelectorAbstraction + + +class PlaywrightDropdown(iDropdownAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def select_from_list_by_value(self, locator, values): + selector_value = SelectorAbstraction.get_selector(locator) + if SelectorAbstraction.is_xpath(locator): + await self.library_ctx.get_current_page().get_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.library_ctx.get_current_page().get_page().selectOption(selector_value, { + 'value': values + }) + + async def select_from_list_by_label(self, locator, labels): + selector_value = SelectorAbstraction.get_selector(locator) + if SelectorAbstraction.is_xpath(locator): + await self.library_ctx.get_current_page().get_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.library_ctx.get_current_page().get_page().selectOption(selector_value, { + 'label': labels + }) \ No newline at end of file diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_element.py b/PuppeteerLibrary/playwright/async_keywords/playwright_element.py new file mode 100644 index 0000000..3514add --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_element.py @@ -0,0 +1,70 @@ +from robot.libraries.BuiltIn import BuiltIn +from PuppeteerLibrary.ikeywords.ielement_async import iElementAsync + + +class PlaywrightElement(iElementAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + ############################## + # Click + ############################## + async def click_element(self, locator: str): + return await self.library_ctx.get_current_page().click_with_selenium_locator(locator) + + async def upload_file(self, locator: str, file_path: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + await element.setInputFiles(file_path) + + ############################## + # Status + ############################## + async def element_should_be_enabled(self, locator: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + is_disabled = await (await element.getProperty('disabled')).jsonValue() + if is_disabled: + raise AssertionError("Element '%s' is disabled. " % locator) + return element + + async def element_should_be_disabled(self, locator: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + is_disabled = await (await element.getProperty('disabled')).jsonValue() + if not is_disabled: + raise AssertionError("Element '%s' is enabled. " % locator) + return element + + async def element_should_be_visible(self, locator:str): + try: + return await self.library_ctx.get_current_page().waitForSelector_with_selenium_locator(locator, 0.1, visible=True, hidden=False) + except: + raise AssertionError("Element '%s' is not be visible. " % locator) + + async def element_should_not_be_visible(self, locator:str): + try: + return await self.library_ctx.get_current_page().waitForSelector_with_selenium_locator(locator, 0.1, visible=False, hidden=True) + except: + raise AssertionError("Element '%s' is visible. " % locator) + + ############################## + # Property + ############################## + async def element_should_contain(self, locator: str, expected: str, ignore_case=False): + text = await self.get_text(locator) + return BuiltIn().should_contain(text, expected, ignore_case=ignore_case) + + async def element_should_not_contain(self, locator: str, expected: str, ignore_case=False): + text = await self.get_text(locator) + return BuiltIn().should_not_contain(text, expected, ignore_case=ignore_case) + + async def get_text(self, locator: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + return (await (await element.getProperty('textContent')).jsonValue()) + + async def element_text_should_be(self, locator: str, expected: str, ignore_case=False): + text = await self.get_text(locator) + return BuiltIn().should_be_equal_as_strings(text, expected, ignore_case=ignore_case) + + async def element_text_should_not_be(self, locator: str, expected: str, ignore_case=False): + text = await self.get_text(locator) + return BuiltIn().should_not_be_equal_as_strings(text, expected, ignore_case=ignore_case) diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_formelement.py b/PuppeteerLibrary/playwright/async_keywords/playwright_formelement.py new file mode 100644 index 0000000..ad04342 --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_formelement.py @@ -0,0 +1,22 @@ +from PuppeteerLibrary.ikeywords.iformelement_async import iFormElementAsync +from PuppeteerLibrary.locators.SelectorAbstraction import SelectorAbstraction + + +class PlaywrightFormElement(iFormElementAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def input_text(self, locator: str, text: str, clear=True): + if clear: + await self._clear_input_text(locator) + await self.library_ctx.get_current_page().type_with_selenium_locator(locator, text) + + async def clear_element_text(self, locator: str): + await self._clear_input_text(locator) + + async def _clear_input_text(self, selenium_locator): + await self.library_ctx.get_current_page().click_with_selenium_locator(selenium_locator, {'clickCount': 3}) + await self.library_ctx.get_current_page().get_page().keyboard.press('Backspace') + + \ No newline at end of file diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_javascript.py b/PuppeteerLibrary/playwright/async_keywords/playwright_javascript.py new file mode 100644 index 0000000..94216b6 --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_javascript.py @@ -0,0 +1,10 @@ +from PuppeteerLibrary.ikeywords.ijavascript_async import iJavascriptAsync + + +class PlaywrightJavascript(iJavascriptAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def execute_javascript(self, code): + return await self.library_ctx.get_current_page().get_page().evaluate(code) diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_mockresponse.py b/PuppeteerLibrary/playwright/async_keywords/playwright_mockresponse.py new file mode 100644 index 0000000..92cc025 --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_mockresponse.py @@ -0,0 +1,26 @@ +import asyncio +import re +from PuppeteerLibrary.base.robotlibcore import keyword +from PuppeteerLibrary.ikeywords.imockresponse_async import iMockResponseAsync + + +class PlaywrightMockResponse(iMockResponseAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def mock_current_page_api_response(self, url, mock_response, method='GET', body=None): + page = self.library_ctx.get_current_page().get_page() + await page.route(url, lambda route, request: asyncio.ensure_future(self.mock_api_response(route, request, mock_response, method, body))) + + async def mock_api_response(self, route, request, mock_response, method, body): + try: + pos_data = (await request.postData()) + except: + pos_data = '' + if body is None or re.search(body, pos_data.replace('\n', '')): + await route.fulfill( + **mock_response + ) + await request.continue_() + \ No newline at end of file diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_mouseevent.py b/PuppeteerLibrary/playwright/async_keywords/playwright_mouseevent.py new file mode 100644 index 0000000..5666140 --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_mouseevent.py @@ -0,0 +1,26 @@ +from robot.libraries.BuiltIn import BuiltIn +from PuppeteerLibrary.ikeywords.imouseevent_async import iMouseEventAsync + + +class PlaywrightMouseEvent(iMouseEventAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def mouse_over(self, locator): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + await element.hover() + + async def mouse_down(self, locator): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + bounding_box = await element.boundingBox() + await self.library_ctx.get_current_page().get_page().mouse.move( + bounding_box['x'] + bounding_box['width'] / 2, + bounding_box['y'] + bounding_box['height'] / 2) + await self.library_ctx.get_current_page().get_page().mouse.down() + + async def mouse_up(self): + await self.library_ctx.get_current_page().get_page().mouse.up() + + async def mouse_move(self, x, y): + await self.library_ctx.get_current_page().get_page().mouse.move(int(x), int(y)) diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_pdf.py b/PuppeteerLibrary/playwright/async_keywords/playwright_pdf.py new file mode 100644 index 0000000..167c6c5 --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_pdf.py @@ -0,0 +1,15 @@ +from PuppeteerLibrary.ikeywords.ipdf_async import iPDFAsync, DEFAULT_FILENAME_PAGE + + +class PlaywrightPDF(iPDFAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def print_as_pdf(self, filename=DEFAULT_FILENAME_PAGE): + path = self._get_pdf_path(filename) + await self.library_ctx.get_current_page().get_page().emulateMedia('screen') + await self.library_ctx.get_current_page().get_page().pdf( + path=path + ) + self.info('Print as pdf: '+path) diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_screenshot.py b/PuppeteerLibrary/playwright/async_keywords/playwright_screenshot.py new file mode 100644 index 0000000..f814109 --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_screenshot.py @@ -0,0 +1,13 @@ +from PuppeteerLibrary.playwright.async_keywords.playwright_waiting import PlaywrightWaiting +from PuppeteerLibrary.ikeywords.iscreenshot_async import iScreenshotAsync + + +class PlaywrightScreenshot(iScreenshotAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def capture_page_screenshot(self, path: str): + return await self.library_ctx.get_current_page().get_page().screenshot( + path=f''+path + ) diff --git a/PuppeteerLibrary/playwright/async_keywords/playwright_waiting.py b/PuppeteerLibrary/playwright/async_keywords/playwright_waiting.py new file mode 100644 index 0000000..f0515ac --- /dev/null +++ b/PuppeteerLibrary/playwright/async_keywords/playwright_waiting.py @@ -0,0 +1,160 @@ +import asyncio +import re +import time +from robot.utils.dotdict import DotDict +from PuppeteerLibrary.ikeywords.iwaiting_async import iWaitingAsync + + +class PlaywrightWaiting(iWaitingAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def wait_for_request_url(self, url, method='GET', body=None, timeout=None): + req = await self.library_ctx.get_current_page().get_page().waitForEvent( + "request", lambda request: re.search(url, request.url) is not None and request.method == method, + self.timestr_to_secs_for_default_timeout(timeout) * 1000 + ) + try: + pos_data = (await req.postData()) + except: + pos_data = '' + + if body is None or re.search(body, pos_data.replace('\n', '')): + log_str = 'Wait for request url: '+req.method+' - '+req.url + if pos_data != '': + log_str + '\n' + pos_data + self.info(log_str) + else: + raise Exception('Can\'t match request body with ' + body + ' \n ' + pos_data) + + return DotDict({ + 'url': req.url, + 'method': req.method, + 'body': pos_data + }) + + async def wait_for_response_url(self, url, status=200, body=None, timeout=None): + res = await self.library_ctx.get_current_page().get_page().waitForEvent( + "response", lambda response: re.search(url, response.url) is not None and response.status == int(status), + self.timestr_to_secs_for_default_timeout(timeout) * 1000 + ) + try: + res_text = (await res.text()) + except: + res_text = '' + if body is None or re.search(body, res_text.replace('\n','')): + log_str = 'Wait for request url: ' + res.url + if res_text != '': + log_str += '\n' + res_text + self.info(log_str) + else: + raise Exception('Can\'t match response body with '+body+' \n '+res_text) + return DotDict({ + 'url': res.url, + 'status': res.status, + 'body': res_text + }) + + async def wait_for_navigation(self, timeout=None): + await self.library_ctx.get_current_page().get_page().waitForNavigation( + timeout=self.timestr_to_secs_for_default_timeout(timeout) * 1000 + ) + + async def wait_until_page_contains_element(self, locator, timeout=None): + return await self._wait_for_selenium_selector(locator, timeout, visible=True, hidden=False) + + async def wait_until_element_is_hidden(self, locator, timeout=None): + return await self._wait_for_selenium_selector(locator, timeout, visible=False, hidden=True) + + async def wait_until_element_is_visible(self, locator, timeout=None): + return await self._wait_for_selenium_selector(locator, timeout, visible=True, hidden=False) + + async def wait_until_page_contains(self, text, timeout=None): + locator = "xpath://*[contains(., %s)]" % self.escape_xpath_value(text) + return await self._wait_for_selenium_selector(locator, timeout, visible=True, hidden=False) + + async def wait_until_page_does_not_contains(self, text, timeout=None): + locator = "xpath://*[contains(., %s)]" % self.escape_xpath_value(text) + return await self._wait_for_selenium_selector(locator, timeout, visible=False, hidden=True) + + async def wait_until_element_contains(self, locator, text, timeout=None): + locator = "xpath://*[contains(., %s)]" % self.escape_xpath_value(text) + return await self._wait_for_selenium_selector(locator, timeout, visible=True, hidden=False) + + async def wait_until_element_does_not_contains(self, locator, text, timeout=None): + async def validate_element_contains_text(): + return (text not in (await (await ( await self.library_ctx.get_current_page(). + querySelector_with_selenium_locator(locator)).getProperty('textContent')).jsonValue())) + return await self._wait_until_worker( + validate_element_contains_text, + self.timestr_to_secs_for_default_timeout(timeout)) + + async def wait_until_location_contains(self, expected, timeout=None): + async def validate_url_contains_text(): + return expected in self.library_ctx.get_current_page().get_page().url + return await self._wait_until_worker( + validate_url_contains_text, + self.timestr_to_secs_for_default_timeout(timeout)) + + async def wait_until_location_does_not_contains(self, expected, timeout=None): + async def validate_url_not_contains_text(): + return expected not in self.library_ctx.get_current_page().get_page().url + return await self._wait_until_worker( + validate_url_not_contains_text, + self.timestr_to_secs_for_default_timeout(timeout)) + + async def wait_until_element_is_enabled(self, locator, timeout=None): + async def validate_is_enabled(): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + is_disabled = await (await element.getProperty('disabled')).jsonValue() + return is_disabled == False + return await self._wait_until_worker( + validate_is_enabled, + self.timestr_to_secs_for_default_timeout(timeout), + 'Element '+locator+' was not enabled.') + + async def wait_until_element_finished_animating(self, locator, timeout=None): + prev_rect_tmp = { 'value': None } + async def check_finished_animating(): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + if prev_rect_tmp['value'] is None: + prev_rect_tmp['value'] = await element.boundingBox() + return False + prev_rect = prev_rect_tmp['value'] + next_rect = await element.boundingBox() + if next_rect['x'] == prev_rect['x'] and next_rect['y'] == prev_rect['y']: + return True + else: + prev_rect_tmp['value'] = next_rect + return False + return await self._wait_until_worker( + check_finished_animating, + self.timestr_to_secs_for_default_timeout(timeout), + 'Element '+locator+' was not enabled.') + + async def _wait_for_selenium_selector(self, selenium_locator, timeout=None, visible=False, hidden=False): + timeout = self.timestr_to_secs_for_default_timeout(timeout) + return await self.library_ctx.get_current_page().waitForSelector_with_selenium_locator(selenium_locator, timeout, visible, hidden) + + async def _wait_until_worker(self, condition, timeout, error=None): + max_time = time.time() + timeout + not_found = None + while time.time() < max_time: + try: + if await condition(): + return + else: + not_found = None + except Exception as err: + not_found = err + await asyncio.sleep(0.2) + raise AssertionError(not_found or error) + + def escape_xpath_value(self, value): + if '"' in value and '\'' in value: + parts_wo_apos = value.split('\'') + return "concat('%s')" % "', \"'\", '".join(parts_wo_apos) + if '\'' in value: + return "\"%s\"" % value + return "'%s'" % value diff --git a/PuppeteerLibrary/playwright/custom_elements/__init__.py b/PuppeteerLibrary/playwright/custom_elements/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/PuppeteerLibrary/playwright/custom_elements/playwright_page.py b/PuppeteerLibrary/playwright/custom_elements/playwright_page.py new file mode 100644 index 0000000..8693e46 --- /dev/null +++ b/PuppeteerLibrary/playwright/custom_elements/playwright_page.py @@ -0,0 +1,107 @@ +from typing import Any +from PuppeteerLibrary.custom_elements.base_page import BasePage +from PuppeteerLibrary.locators.SelectorAbstraction import SelectorAbstraction +try: + from playwright.page import Page +except Exception: + from pyppeteer.page import Page + print('import playwright error') + +class PlaywrightPage(BasePage): + + def __init__(self, page: Page): + self.page = page + self.selected_iframe = None + + def get_page(self) -> Page: + return self.page + + async def goto(self, url: str): + return await self.page.goto(url) + + async def go_back(self): + return await self.page.goBack() + + async def reload_page(self): + return await self.page.reload() + + async def title(self): + return await self.page.title() + + async def set_viewport_size(self, width: int, height: int): + return await self.page.setViewportSize(width, height) + + ############ + # Click + ############ + async def click_with_selenium_locator(self, selenium_locator: str, options: dict = None, **kwargs: Any): + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if options is None: + options = {} + if self.selected_iframe is not None: + return await self.selected_iframe.click(selector=selector_value, **options) + else: + return await self.page.click(selector=selector_value, **options) + + ############ + # Type + ############ + async def type_with_selenium_locator(self, selenium_locator: str, text: str, options: dict = None, **kwargs: Any): + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if options is None: + options = {} + if self.selected_iframe is not None: + return await self.selected_iframe.type(selector=selector_value, text=text, **options) + else: + return await self.page.type(selector=selector_value, text=text, **options) + + ############ + # Wait + ############ + async def waitForSelector_with_selenium_locator(self, selenium_locator: str, timeout: float, visible=False, hidden=False): + options = { + 'timeout': timeout * 1000, + 'state': 'visible' + } + if visible is True: + options['state'] = 'visible' + if hidden is True: + options['state'] = 'hidden' + + selector_value = SelectorAbstraction.get_selector(selenium_locator) + return await self.get_page().waitForSelector( + selector=selector_value, + timeout=options['timeout'], + state=options['state']) + + ############ + # Query + ############ + async def querySelector(self, selector: str): + if self.selected_iframe is not None: + return await self.selected_iframe.querySelector(selector=selector) + else: + return await self.get_page().querySelector(selector=selector) + + async def querySelectorAll_with_selenium_locator(self, selenium_locator: str): + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if SelectorAbstraction.is_xpath(selenium_locator): + return await self.get_page().xpath(selector_value) + else: + return await self.get_page().querySelectorAll(selector_value) + + async def querySelector_with_selenium_locator(self, selenium_locator: str): + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if SelectorAbstraction.is_xpath(selenium_locator): + return (await self.get_page().xpath(selector_value))[0] + else: + return await self.get_page().querySelector(selector_value) + + ############################## + # iframe + ############################## + def set_current_iframe(self, iframe): + self.selected_iframe = iframe + + def unselect_iframe(self): + self.selected_iframe = None diff --git a/PuppeteerLibrary/playwright/playwright_context.py b/PuppeteerLibrary/playwright/playwright_context.py new file mode 100644 index 0000000..77516d3 --- /dev/null +++ b/PuppeteerLibrary/playwright/playwright_context.py @@ -0,0 +1,103 @@ +from PuppeteerLibrary.playwright.async_keywords.playwright_mockresponse import PlaywrightMockResponse +import asyncio +from PuppeteerLibrary.custom_elements.base_page import BasePage +from PuppeteerLibrary.playwright.custom_elements.playwright_page import PlaywrightPage +from PuppeteerLibrary.playwright.async_keywords.playwright_formelement import PlaywrightFormElement +from PuppeteerLibrary.playwright.async_keywords.playwright_dropdown import PlaywrightDropdown +from PuppeteerLibrary.playwright.async_keywords.playwright_alert import PlaywrightAlert +from PuppeteerLibrary.playwright.async_keywords.playwright_screenshot import PlaywrightScreenshot +from PuppeteerLibrary.playwright.async_keywords.playwright_waiting import PlaywrightWaiting +from PuppeteerLibrary.playwright.async_keywords.playwright_element import PlaywrightElement +from PuppeteerLibrary.playwright.async_keywords.playwright_dropdown import PlaywrightDropdown +from PuppeteerLibrary.playwright.async_keywords.playwright_mouseevent import PlaywrightMouseEvent +from PuppeteerLibrary.playwright.async_keywords.playwright_browsermanagement import PlaywrightBrowserManagement +from PuppeteerLibrary.playwright.async_keywords.playwright_pdf import PlaywrightPDF +from PuppeteerLibrary.playwright.async_keywords.playwright_javascript import PlaywrightJavascript +from PuppeteerLibrary.library_context.ilibrary_context import iLibraryContext +try: + from playwright import async_playwright + from playwright.playwright import Playwright as AsyncPlaywright + from playwright.browser import Browser +except ImportError: + print('import playwright error') + + +class PlaywrightContext(iLibraryContext): + + playwright: any = None + browser: any = None + current_page: any = None + current_iframe = None + + def __init__(self, browser_type: str): + super().__init__(browser_type) + + async def start_server(self, options: dict=None): + self.playwright = await async_playwright().start() + if self.browser_type == "webkit": + self.browser = await self.playwright.webkit.launch(headless=False) + elif self.browser_type == "firefox": + self.browser = await self.playwright.firefox.launch(headless=False) + + async def stop_server(self): + await self.playwright.stop() + self._reset_server_context() + + def is_server_started(self) -> bool: + if self.browser is not None: + return True + return False + + async def create_new_page(self, options: dict=None) -> BasePage: + device_options = {} + if 'emulate' in options: + device_options = self.playwright.devices[options['emulate']] + new_page = await self.browser.newPage(**device_options) + self.current_page = PlaywrightPage(new_page) + return self.current_page + + def get_current_page(self) -> BasePage: + return self.current_page + + def set_current_page(self, page: any) -> BasePage: + self.current_page = PlaywrightPage(page) + return self.current_page + + async def get_all_pages(self): + return self.browser.contexts[0].pages + + def get_browser_context(self): + return self.browser + + async def close_browser_context(self): + if self.browser is not None: + try: + await asyncio.wait_for(self.browser.close(), timeout=3) + except asyncio.TimeoutError: + None + self._reset_context() + + def get_async_keyword_group(self, keyword_group_name: str): + switcher = { + "AlertKeywords": PlaywrightAlert(self), + "BrowserManagementKeywords": PlaywrightBrowserManagement(self), + "DropdownKeywords": PlaywrightDropdown(self), + "ElementKeywords": PlaywrightElement(self), + "FormElementKeywords": PlaywrightFormElement(self), + "JavascriptKeywords": PlaywrightJavascript(self), + "MockResponseKeywords": PlaywrightMockResponse(self), + "MouseEventKeywords": PlaywrightMouseEvent(self), + "PDFKeywords": PlaywrightPDF(self), + "ScreenshotKeywords": PlaywrightScreenshot(self), + "WaitingKeywords": PlaywrightWaiting(self) + } + return switcher.get(keyword_group_name) + + def _reset_context(self): + self.browser = None + self.current_page = None + self.current_iframe = None + + def _reset_server_context(self): + self._reset_context() + self.playwright = None diff --git a/PuppeteerLibrary/puppeteer/__init__.py b/PuppeteerLibrary/puppeteer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/PuppeteerLibrary/puppeteer/async_keywords/__init__.py b/PuppeteerLibrary/puppeteer/async_keywords/__init__.py new file mode 100644 index 0000000..0a8411f --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/__init__.py @@ -0,0 +1 @@ +from .puppeteer_browsermanagement import PuppeteerBrowserManagement \ No newline at end of file diff --git a/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_alert.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_alert.py new file mode 100644 index 0000000..8696af4 --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_alert.py @@ -0,0 +1,17 @@ +import asyncio +from PuppeteerLibrary.ikeywords.ialert_async import iAlertAsync + + +class PuppeteerAlert(iAlertAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def handle_alert(self, action, prompt_text=''): + return self.library_ctx.get_current_page().get_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/puppeteer/async_keywords/puppeteer_browsermanagement.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_browsermanagement.py new file mode 100644 index 0000000..734f0b1 --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_browsermanagement.py @@ -0,0 +1,97 @@ +import asyncio +import re +import time +from PuppeteerLibrary.ikeywords.ibrowsermanagement_async import iBrowserManagementAsync + + +class PuppeteerBrowserManagement(iBrowserManagementAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def maximize_browser_window(self, width=1366, height=768): + return await self.ctx.get_current_page().setViewport({ + 'width': width, + 'height': height + }) + + async def go_to(self, url): + return await self.library_ctx.get_current_page().goto(url) + + async def go_back(self): + return await self.library_ctx.get_current_page().go_back() + + async def reload_page(self): + return await self.library_ctx.get_current_page().reload_page() + + async def get_window_count(self): + pass + + async def wait_for_new_window_open(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.library_ctx.get_all_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)) + + async def _get_window_count_async(self): + pages = await self.library_ctx.get_all_pages() + for page in pages: + # Workaround: for force pages re-cache + try: + await page.title() + except: + return [] + return len(await self.library_ctx.get_all_pages()) + + async def switch_window(self, locator='MAIN'): + pages = await self.library_ctx.get_all_pages() + if locator == 'MAIN': + page = pages[0] + await page.bringToFront() + return self.library_ctx.set_current_page(page) + + elif locator == 'NEW': + page = pages[-1] + await page.bringToFront() + return self.library_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.library_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.library_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.') + + ############################## + # iFrame + ############################## + async def select_frame(self, locator: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + iframe = await element.contentFrame() + self.library_ctx.get_current_page().set_current_iframe(iframe) + + def unselect_iframe(self): + self.library_ctx.get_current_page().unselect_iframe() + diff --git a/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_dropdown.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_dropdown.py new file mode 100644 index 0000000..66b3202 --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_dropdown.py @@ -0,0 +1,33 @@ +from PuppeteerLibrary.ikeywords.idropdown_async import iDropdownAsync +from PuppeteerLibrary.locators.SelectorAbstraction import SelectorAbstraction + + +class PuppeteerDropdown(iDropdownAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def select_from_list_by_value(self, locator, values): + selector_value = SelectorAbstraction.get_selector(locator) + if SelectorAbstraction.is_xpath(locator): + await self.library_ctx.get_current_page().get_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.library_ctx.get_current_page().get_page().select(selector_value, values) + + async def select_from_list_by_label(self, locator, labels): + selector_value = SelectorAbstraction.get_selector(locator) + if SelectorAbstraction.is_xpath(locator): + await self.library_ctx.get_current_page().get_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.library_ctx.get_current_page().get_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/puppeteer/async_keywords/puppeteer_element.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_element.py new file mode 100644 index 0000000..c6253f8 --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_element.py @@ -0,0 +1,71 @@ +from robot.libraries.BuiltIn import BuiltIn +from PuppeteerLibrary.ikeywords.ielement_async import iElementAsync + + +class PuppeteerElement(iElementAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + ############################## + # Click + ############################## + async def click_element(self, locator: str): + return await self.library_ctx.get_current_page().click_with_selenium_locator(locator) + + async def upload_file(self, locator: str, file_path: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + await element.uploadFile(file_path) + + ############################## + # Status + ############################## + async def element_should_be_enabled(self, locator: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + is_disabled = await (await element.getProperty('disabled')).jsonValue() + if is_disabled: + raise AssertionError("Element '%s' is disabled. " % locator) + return element + + async def element_should_be_disabled(self, locator: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + is_disabled = await (await element.getProperty('disabled')).jsonValue() + if not is_disabled: + raise AssertionError("Element '%s' is enabled. " % locator) + return element + + async def element_should_be_visible(self, locator:str): + try: + return await self.library_ctx.get_current_page().waitForSelector_with_selenium_locator(locator, 0.1, visible=True, hidden=False) + except: + raise AssertionError("Element '%s' is not be visible. " % locator) + + async def element_should_not_be_visible(self, locator:str): + try: + return await self.library_ctx.get_current_page().waitForSelector_with_selenium_locator(locator, 0.1, visible=False, hidden=True) + except: + raise AssertionError("Element '%s' is visible. " % locator) + + ############################## + # Property + ############################## + async def element_should_contain(self, locator: str, expected: str, ignore_case=False): + text = await self.get_text(locator) + return BuiltIn().should_contain(text, expected, ignore_case=ignore_case) + + async def element_should_not_contain(self, locator: str, expected: str, ignore_case=False): + text = await self.get_text(locator) + return BuiltIn().should_not_contain(text, expected, ignore_case=ignore_case) + + async def get_text(self, locator: str): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + return (await (await element.getProperty('textContent')).jsonValue()) + + async def element_text_should_be(self, locator: str, expected: str, ignore_case=False): + text = await self.get_text(locator) + return BuiltIn().should_be_equal_as_strings(text, expected, ignore_case=ignore_case) + + async def element_text_should_not_be(self, locator: str, expected: str, ignore_case=False): + text = await self.get_text(locator) + return BuiltIn().should_not_be_equal_as_strings(text, expected, ignore_case=ignore_case) + diff --git a/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_formelement.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_formelement.py new file mode 100644 index 0000000..b8dae0b --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_formelement.py @@ -0,0 +1,20 @@ +from robot.libraries.BuiltIn import BuiltIn +from PuppeteerLibrary.ikeywords.iformelement_async import iFormElementAsync + + +class PuppeteerFormElement(iFormElementAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def input_text(self, locator: str, text: str, clear=True): + if clear: + await self._clear_input_text(locator) + await self.library_ctx.get_current_page().type_with_selenium_locator(locator, text) + + async def clear_element_text(self, locator: str): + await self._clear_input_text(locator) + + async def _clear_input_text(self, selenium_locator): + await self.library_ctx.get_current_page().click_with_selenium_locator(selenium_locator, {'clickCount': 3}) + await self.library_ctx.get_current_page().get_page().keyboard.press('Backspace') diff --git a/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_javascript.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_javascript.py new file mode 100644 index 0000000..c54b53a --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_javascript.py @@ -0,0 +1,10 @@ +from PuppeteerLibrary.ikeywords.ijavascript_async import iJavascriptAsync + + +class PuppeteerJavascript(iJavascriptAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def execute_javascript(self, code): + return await self.library_ctx.get_current_page().get_page().evaluate(code) diff --git a/PuppeteerLibrary/keywords/mockresponse_async.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_mockresponse.py similarity index 60% rename from PuppeteerLibrary/keywords/mockresponse_async.py rename to PuppeteerLibrary/puppeteer/async_keywords/puppeteer_mockresponse.py index ce91f41..b857577 100644 --- a/PuppeteerLibrary/keywords/mockresponse_async.py +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_mockresponse.py @@ -2,23 +2,22 @@ import re from pyppeteer.network_manager import Request from PuppeteerLibrary.base.robotlibcore import keyword -from PuppeteerLibrary.base.librarycomponent import LibraryComponent +from PuppeteerLibrary.ikeywords.imockresponse_async import iMockResponseAsync -class MockResponseKeywordsAsync(LibraryComponent): +class PuppeteerMockResponse(iMockResponseAsync): - def __init__(self, ctx): - self.ctx = ctx + def __init__(self, library_ctx): + super().__init__(library_ctx) - @keyword - async def mock_current_page_api_response_async(self, url, mock_response, method='GET', body=None): - page = self.ctx.get_current_page() + async def mock_current_page_api_response(self, url, mock_response, method='GET', body=None): + page = self.library_ctx.get_current_page().get_page() await page.setRequestInterception(True) page.on('request', lambda request: asyncio.ensure_future(self.mock_api_response(request, url, mock_response, method, body))) async def mock_api_response(self, request: Request, url, mock_response, method, body): - if re.search(url, request.url) is not None and request.method == method: + if re.search(re.escape(url), request.url) is not None and request.method == method: try: pos_data = (await request.postData()) except: @@ -26,4 +25,3 @@ async def mock_api_response(self, request: Request, url, mock_response, method, if body is None or re.search(body, pos_data.replace('\n', '')): return await request.respond(mock_response) await request.continue_() - diff --git a/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_mouseevent.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_mouseevent.py new file mode 100644 index 0000000..39ad689 --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_mouseevent.py @@ -0,0 +1,25 @@ +from PuppeteerLibrary.ikeywords.imouseevent_async import iMouseEventAsync + + +class PuppeteerMouseEvent(iMouseEventAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def mouse_over(self, locator): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + await element.hover() + + async def mouse_down(self, locator): + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) + bounding_box = await element.boundingBox() + await self.library_ctx.get_current_page().get_page().mouse.move( + bounding_box['x'] + bounding_box['width'] / 2, + bounding_box['y'] + bounding_box['height'] / 2) + await self.library_ctx.get_current_page().get_page().mouse.down() + + async def mouse_up(self): + await self.library_ctx.get_current_page().get_page().mouse.up() + + async def mouse_move(self, x, y): + await self.library_ctx.get_current_page().get_page().mouse.move(int(x), int(y)) diff --git a/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_pdf.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_pdf.py new file mode 100644 index 0000000..a7b4f2c --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_pdf.py @@ -0,0 +1,13 @@ +from PuppeteerLibrary.ikeywords.ipdf_async import iPDFAsync, DEFAULT_FILENAME_PAGE + + +class PuppeteerPDF(iPDFAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def print_as_pdf(self, filename=DEFAULT_FILENAME_PAGE): + path = self._get_pdf_path(filename) + await self.library_ctx.get_current_page().get_page().emulateMedia('screen') + await self.library_ctx.get_current_page().get_page().pdf({'path': path}) + self.info('Print as pdf: '+path) diff --git a/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_screenshot.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_screenshot.py new file mode 100644 index 0000000..53f9290 --- /dev/null +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_screenshot.py @@ -0,0 +1,12 @@ +from PuppeteerLibrary.ikeywords.iscreenshot_async import iScreenshotAsync + + +class PuppeteerScreenshot(iScreenshotAsync): + + def __init__(self, library_ctx): + super().__init__(library_ctx) + + async def capture_page_screenshot(self, path: str): + return await self.library_ctx.get_current_page().get_page().screenshot( + {'path': path} + ) diff --git a/PuppeteerLibrary/keywords/waiting_async.py b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_waiting.py similarity index 56% rename from PuppeteerLibrary/keywords/waiting_async.py rename to PuppeteerLibrary/puppeteer/async_keywords/puppeteer_waiting.py index 967709f..c23149b 100644 --- a/PuppeteerLibrary/keywords/waiting_async.py +++ b/PuppeteerLibrary/puppeteer/async_keywords/puppeteer_waiting.py @@ -1,16 +1,29 @@ import asyncio -import time import re -from robot.utils import DotDict -from PuppeteerLibrary.base.librarycomponent import LibraryComponent -from PuppeteerLibrary.base.robotlibcore import keyword +import time +from robot.utils.dotdict import DotDict +from PuppeteerLibrary.ikeywords.iwaiting_async import iWaitingAsync + + +class PuppeteerWaiting(iWaitingAsync): + def __init__(self, library_ctx): + super().__init__(library_ctx) -class WaitingKeywordsAsync(LibraryComponent): + async def _wait_for_selenium_selector(self, selenium_locator, timeout=None, visible=False, hidden=False): + timeout = self.timestr_to_secs_for_default_timeout(timeout) + return await self.library_ctx.get_current_page().waitForSelector_with_selenium_locator(selenium_locator, timeout, visible, hidden) + + def escape_xpath_value(self, value): + if '"' in value and '\'' in value: + parts_wo_apos = value.split('\'') + return "concat('%s')" % "', \"'\", '".join(parts_wo_apos) + if '\'' in value: + return "\"%s\"" % value + return "'%s'" % value - @keyword - async def wait_for_request_url_async(self, url, method='GET', body=None, timeout=None): - req = await self.ctx.get_current_page().waitForRequest( + async def wait_for_request_url(self, url, method='GET', body=None, timeout=None): + req = await self.library_ctx.get_current_page().get_page().waitForRequest( lambda req: re.search(url, req.url) is not None and req.method == method , options={ @@ -36,9 +49,8 @@ async def wait_for_request_url_async(self, url, method='GET', body=None, timeout 'body': pos_data }) - @keyword - async def wait_for_response_url_async(self, url, status=200, body=None, timeout=None): - res = await self.ctx.get_current_page().waitForResponse( + async def wait_for_response_url(self, url, status=200, body=None, timeout=None): + res = await self.library_ctx.get_current_page().get_page().waitForResponse( lambda res: re.search(url, res.url) is not None and res.status == int(status) , options={ @@ -61,87 +73,73 @@ async def wait_for_response_url_async(self, url, status=200, body=None, timeout= 'body': res_text }) - @keyword - async def wait_for_navigation_async(self, timeout=None): - await self.ctx.get_current_page().waitForNavigation(options={ + async def wait_for_navigation(self, timeout=None): + await self.library_ctx.get_current_page().get_page().waitForNavigation( + options={ 'timeout': self.timestr_to_secs_for_default_timeout(timeout) * 1000 }) - @keyword - async def wait_for_selenium_selector(self, selenium_locator, timeout=None, visible=False, hidden=False): - timeout = self.timestr_to_secs_for_default_timeout(timeout) - return await self.ctx.get_current_page().waitForSelector_with_selenium_locator(selenium_locator, timeout, visible, hidden) - - @keyword - async def wait_until_page_contains_element_async(self, selenium_locator, timeout=None): - return await self.wait_for_selenium_selector(selenium_locator, timeout, visible=False, hidden=False) + async def wait_until_page_contains_element(self, locator, timeout=None): + return await self._wait_for_selenium_selector(locator, timeout, visible=True, hidden=False) - @keyword - async def wait_until_element_is_hidden_async(self, locator, timeout=None): - return await self.wait_for_selenium_selector(locator, timeout, visible=False, hidden=True) + async def wait_until_element_is_hidden(self, locator, timeout=None): + return await self._wait_for_selenium_selector(locator, timeout, visible=False, hidden=True) - @keyword - async def wait_until_element_is_visible_async(self, locator, timeout=None): - return await self.wait_for_selenium_selector(locator, timeout, visible=True, hidden=False) + async def wait_until_element_is_visible(self, locator, timeout=None): + return await self._wait_for_selenium_selector(locator, timeout, visible=True, hidden=False) - @keyword - async def wait_until_page_contains_async(self, text, timeout=None): + async def wait_until_page_contains(self, text, timeout=None): locator = "xpath://*[contains(., %s)]" % self.escape_xpath_value(text) - return await self.wait_for_selenium_selector(locator, timeout) + return await self._wait_for_selenium_selector(locator, timeout, visible=True, hidden=False) - @keyword - async def wait_until_page_does_not_contains_async(self, text, timeout=None): + async def wait_until_page_does_not_contains(self, text, timeout=None): locator = "xpath://*[contains(., %s)]" % self.escape_xpath_value(text) - return await self.wait_for_selenium_selector(locator, timeout, visible=False, hidden=True) + return await self._wait_for_selenium_selector(locator, timeout, visible=False, hidden=True) - @keyword - async def wait_until_element_contains_async(self, selenium_locator, text, timeout=None): + async def wait_until_element_contains(self, locator, text, timeout=None): async def validate_element_contains_text(): - return (text in (await (await ( await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator)).getProperty('textContent')).jsonValue())) + return (text in (await (await ( await self.library_ctx.get_current_page(). + querySelector_with_selenium_locator(locator)).getProperty('textContent')).jsonValue())) return await self._wait_until_worker( validate_element_contains_text, self.timestr_to_secs_for_default_timeout(timeout)) - @keyword - async def wait_until_element_does_not_contains_async(self, selenium_locator, text, timeout=None): + async def wait_until_element_does_not_contains(self, locator, text, timeout=None): async def validate_element_contains_text(): - return (text not in (await (await ( await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator)).getProperty('textContent')).jsonValue())) + return (text not in (await (await ( await self.library_ctx.get_current_page(). + querySelector_with_selenium_locator(locator)).getProperty('textContent')).jsonValue())) return await self._wait_until_worker( validate_element_contains_text, self.timestr_to_secs_for_default_timeout(timeout)) - @keyword - async def wait_until_location_contains_async(self, expected, timeout=None): + async def wait_until_location_contains(self, expected, timeout=None): async def validate_url_contains_text(): - return expected in self.ctx.get_current_page().url + return expected in self.library_ctx.get_current_page().get_page().url return await self._wait_until_worker( validate_url_contains_text, self.timestr_to_secs_for_default_timeout(timeout)) - @keyword - async def wait_until_location_does_not_contains_async(self, expected, timeout=None): + async def wait_until_location_does_not_contains(self, expected, timeout=None): async def validate_url_not_contains_text(): - return expected not in self.ctx.get_current_page().url + return expected not in self.library_ctx.get_current_page().get_page().url return await self._wait_until_worker( validate_url_not_contains_text, self.timestr_to_secs_for_default_timeout(timeout)) - @keyword - async def wait_until_element_is_enabled_async(self, selenium_locator, timeout=None): + async def wait_until_element_is_enabled(self, locator, timeout=None): async def validate_is_enabled(): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) is_disabled = await (await element.getProperty('disabled')).jsonValue() return is_disabled == False return await self._wait_until_worker( validate_is_enabled, self.timestr_to_secs_for_default_timeout(timeout), - 'Element '+selenium_locator+' was not enabled.') + 'Element '+locator+' was not enabled.') - @keyword - async def wait_until_element_finished_animating_async(self, selenium_locator, timeout=None): + async def wait_until_element_finished_animating(self, locator, timeout=None): prev_rect_tmp = { 'value': None } async def check_finished_animating(): - element = await self.ctx.get_current_page().querySelector_with_selenium_locator(selenium_locator) + element = await self.library_ctx.get_current_page().querySelector_with_selenium_locator(locator) if prev_rect_tmp['value'] is None: prev_rect_tmp['value'] = await element.boundingBox() return False @@ -155,7 +153,7 @@ async def check_finished_animating(): return await self._wait_until_worker( check_finished_animating, self.timestr_to_secs_for_default_timeout(timeout), - 'Element '+selenium_locator+' was not enabled.') + 'Element '+locator+' was not enabled.') async def _wait_until_worker(self, condition, timeout, error=None): max_time = time.time() + timeout diff --git a/PuppeteerLibrary/puppeteer/custom_elements/__init__.py b/PuppeteerLibrary/puppeteer/custom_elements/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/PuppeteerLibrary/puppeteer/custom_elements/puppeteer_page.py b/PuppeteerLibrary/puppeteer/custom_elements/puppeteer_page.py new file mode 100644 index 0000000..b1f92ca --- /dev/null +++ b/PuppeteerLibrary/puppeteer/custom_elements/puppeteer_page.py @@ -0,0 +1,139 @@ +from typing import Any +from pyppeteer.page import Page +from PuppeteerLibrary.custom_elements.base_page import BasePage +from PuppeteerLibrary.locators.SelectorAbstraction import SelectorAbstraction + + +class PuppeteerPage(BasePage): + + def __init__(self, page: Page): + self.page = page + self.selected_iframe = None + + def get_page(self) -> Page: + return self.page + + async def goto(self, url: str): + return await self.page.goto(url) + + async def go_back(self): + return await self.page.goBack() + + async def reload_page(self): + return await self.page.reload() + + async def title(self): + return await self.page.title() + + async def set_viewport_size(self, width: int, height: int): + self.page.setViewport({ + width: width, + height: height + }) + + ############ + # Click + ############ + async def click_with_selenium_locator(self, selenium_locator: str, options: dict = None, **kwargs: Any): + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if SelectorAbstraction.is_xpath(selenium_locator): + await self.click_xpath(selector_value, options, **kwargs) + else: + await self.click(selector_value, options, **kwargs) + + async def click(self, selector: str, options: dict = None, **kwargs: Any): + if self.selected_iframe is not None: + return await self.selected_iframe.click(selector=selector, options=options, kwargs=kwargs) + else: + return await self.page.click(selector=selector, options=options, kwargs=kwargs) + + async def click_xpath(self, selector: str, options: dict = None, **kwargs: Any): + if self.selected_iframe is not None: + elements = await self.selected_iframe.xpath(selector) + return await elements[0].click(options, **kwargs) + else: + elements = await self.page.xpath(selector) + return await elements[0].click(options, **kwargs) + + ############ + # Type + ############ + async def type_with_selenium_locator(self, selenium_locator: str, text: str, options: dict = None, **kwargs: Any): + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if SelectorAbstraction.is_xpath(selenium_locator): + await self.type_xpath(selector=selector_value, text=text, options=options, kwargs=kwargs) + else: + await self.type(selector=selector_value, text=text, options=options, kwargs=kwargs) + + async def type(self, selector, text: str, options: dict = None, **kwargs: Any): + if self.selected_iframe is not None: + return await self.selected_iframe.type(selector=selector, text=text, options=options, kwargs=kwargs) + else: + return await self.page.type(selector=selector, text=text, options=options, kwargs=kwargs) + + async def type_xpath(self, selector, text: str, options: dict = None, **kwargs: Any): + if self.selected_iframe is not None: + elements = await self.selected_iframe.xpath(selector) + await elements[0].type(text, options, **kwargs) + else: + elements = await self.page.xpath(selector) + await elements[0].type(text, options, **kwargs) + + ############ + # Wait + ############ + async def waitForSelector_with_selenium_locator(self, selenium_locator: str, timeout: float, visible=False, hidden=False): + options = { + 'timeout': timeout * 1000, + 'visible': visible, + 'hidden': hidden + } + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if SelectorAbstraction.is_xpath(selenium_locator): + return await self._waitForXPath(xpath=selector_value, options=options) + else: + return await self._waitForSelector(selector=selector_value, options=options) + + async def _waitForSelector(self, selector: str, options: dict = None): + if self.selected_iframe is None: + return await self.page.waitForSelector(selector=selector, options=options) + else: + return await self.selected_iframe.waitForSelector(selector=selector, options=options) + + async def _waitForXPath(self, xpath: str, options: dict = None): + if self.selected_iframe is None: + return await self.page.waitForXPath(xpath=xpath, options=options) + else: + return await self.selected_iframe.waitForXPath(xpath=xpath, options=options) + + ############ + # Query + ############ + async def querySelector(self, selector: str): + if self.selected_iframe is not None: + return await self.selected_iframe.querySelector(selector=selector) + else: + return await self.page.querySelector(selector=selector) + + async def querySelectorAll_with_selenium_locator(self, selenium_locator: str): + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if SelectorAbstraction.is_xpath(selenium_locator): + return await self.page.xpath(selector_value) + else: + return await self.page.querySelectorAll(selector_value) + + async def querySelector_with_selenium_locator(self, selenium_locator: str): + selector_value = SelectorAbstraction.get_selector(selenium_locator) + if SelectorAbstraction.is_xpath(selenium_locator): + return (await self.page.xpath(selector_value))[0] + else: + return await self.page.querySelector(selector_value) + + ############################## + # iframe + ############################## + def set_current_iframe(self, iframe): + self.selected_iframe = iframe + + def unselect_iframe(self): + self.selected_iframe = None diff --git a/PuppeteerLibrary/puppeteer/puppeteer_context.py b/PuppeteerLibrary/puppeteer/puppeteer_context.py new file mode 100644 index 0000000..5301588 --- /dev/null +++ b/PuppeteerLibrary/puppeteer/puppeteer_context.py @@ -0,0 +1,124 @@ +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_mockresponse import PuppeteerMockResponse +import sys +from pyppeteer import launch +from pyppeteer.browser import Browser +from PuppeteerLibrary.custom_elements.base_page import BasePage +from PuppeteerLibrary.library_context.ilibrary_context import iLibraryContext +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_alert import PuppeteerAlert +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_screenshot import PuppeteerScreenshot +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_waiting import PuppeteerWaiting +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_browsermanagement import PuppeteerBrowserManagement +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_dropdown import PuppeteerDropdown +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_element import PuppeteerElement +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_formelement import PuppeteerFormElement +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_mouseevent import PuppeteerMouseEvent +from PuppeteerLibrary.puppeteer.custom_elements.puppeteer_page import PuppeteerPage +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_pdf import PuppeteerPDF +from PuppeteerLibrary.puppeteer.async_keywords.puppeteer_javascript import PuppeteerJavascript +from PuppeteerLibrary.utils.device_descriptors import DEVICE_DESCRIPTORS + + +class PuppeteerContext(iLibraryContext): + + browser: Browser = None + contexts = {} + current_page = None + current_iframe = None + + debug_mode: bool = False + debug_mode_options: dict = { + 'slowMo': 200, + 'devtools': False + } + + def __init__(self, browser_type: str): + super().__init__(browser_type) + + async def start_server(self, options: dict=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.debug_mode is True: + merged_options = {**merged_options, **self.debug_mode_options} + + if 'win' not in sys.platform.lower(): + default_args = ['--no-sandbox', '--disable-setuid-sandbox'] + + self.browser = await launch( + headless=merged_options['headless'], + slowMo=merged_options['slowMo'], + devtools=merged_options['devtools'], + defaultViewport={ + 'width': merged_options['width'], + 'height': merged_options['height'] + }, + args=default_args) + + async def stop_server(self): + await self.browser.close() + self._reset_context() + + def is_server_started(self) -> bool: + if self.browser is not None: + return True + return False + + async def create_new_page(self, options: dict=None) -> BasePage: + new_page = await self.browser.newPage() + self.current_page = PuppeteerPage(new_page) + if 'emulate' in options: + await self.current_page.get_page().emulate(DEVICE_DESCRIPTORS[options['emulate']]) + return self.current_page + + def get_current_page(self) -> BasePage: + return self.current_page + + def set_current_page(self, page: any) -> BasePage: + self.current_page = PuppeteerPage(page) + return self.current_page + + async def get_all_pages(self): + return await self.browser.pages() + + def get_browser_context(self): + return self.browser + + async def close_browser_context(self): + await self.browser.close() + + def get_async_keyword_group(self, keyword_group_name: str): + switcher = { + "AlertKeywords": PuppeteerAlert(self), + "BrowserManagementKeywords": PuppeteerBrowserManagement(self), + "DropdownKeywords": PuppeteerDropdown(self), + "ElementKeywords": PuppeteerElement(self), + "FormElementKeywords": PuppeteerFormElement(self), + "JavascriptKeywords": PuppeteerJavascript(self), + "MockResponseKeywords": PuppeteerMockResponse(self), + "MouseEventKeywords": PuppeteerMouseEvent(self), + "PDFKeywords": PuppeteerPDF(self), + "ScreenshotKeywords": PuppeteerScreenshot(self), + "WaitingKeywords": PuppeteerWaiting(self) + } + return switcher.get(keyword_group_name) + + def _reset_context(self): + browser = None + contexts = {} + current_page = None + current_iframe = None + debug_mode = False + debug_mode_options = { + 'slowMo': 200, + 'devtools': False + } diff --git a/PuppeteerLibrary/utils/device_descriptors.py b/PuppeteerLibrary/utils/device_descriptors.py index 5f04581..b5d4a1f 100644 --- a/PuppeteerLibrary/utils/device_descriptors.py +++ b/PuppeteerLibrary/utils/device_descriptors.py @@ -430,6 +430,17 @@ 'isLandscape': True } }, + 'iPhone 11': { + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1', + 'viewport': { + 'width': 414, + 'height': 896, + 'deviceScaleFactor': 2, + 'isMobile': True, + 'hasTouch': True, + 'isLandscape': False + } + }, 'JioPhone 2': { 'userAgent': 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5', 'viewport': { diff --git a/README.md b/README.md index 2a4bfcc..e58ffb6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # robotframework-puppeteer Robot Framework Puppeteer Library powered by [Pyppeteer](https://github.com/pyppeteer/pyppeteer). -Improve automated web testing with native functionality from [Puppeteer](https://github.com/puppeteer/puppeteer) by Google. +Improve automated web testing with chrome native functionality from [Puppeteer](https://github.com/puppeteer/puppeteer) by Google and webkit from [Playwright](https://github.com/microsoft/playwright-python). More detail please visit [Robot Framework Puppeteer Homepage](https://qahive.github.io/robotframework-puppeteer.github.io/) @@ -14,6 +14,15 @@ Example: - _Intercepter javascript function_ +Browser Support +--------------------- +| | Support | Library | +| :--- | :---: | :---: | :---: | +| Chromium | ✅ | Puppeteer | +| WebKit | ✅ | Playwright | +| Firefox | ✅ | Playwright | + + Keyword documentation --------------------- See [`keyword documentation`](https://qahive.github.io/robotframework-puppeteer/PuppeteerLibrary.html) for available keywords and more information about the library in general. @@ -25,6 +34,7 @@ Installation The recommended installation method is using pip_:: pip install --upgrade robotframework-puppeteerlibrary + python -m playwright install Or manually install by running following command diff --git a/requirements.txt b/requirements.txt index 833876f..a78446c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ versioneer>=0.18 robotframework>=3.2.1 +playwright==0.151.0 # pyppeteer>=0.2.2 -git+https://github.com/qahive/pyppeteer.git@dev +git+https://github.com/qahive/pyppeteer.git@dev2 robotframework-libdoc2json>=0.4 \ No newline at end of file diff --git a/setup.py b/setup.py index 38ace87..e1fa57c 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,8 @@ platforms='any', install_requires=[ 'robotframework>=3.2.1', - 'pyppeteer>=0.2.2' + 'playwright>=0.151.0', + 'pyppeteer>=0.2.2', ], # python_requires='>3.5', # test_suite='nose.collector',