├── .all-contributorsrc ├── .coveragerc ├── .dockerignore ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── dockers.yml │ ├── on-push.yml │ └── on-release.yml ├── .gitignore ├── .gitpod.yml ├── .husky ├── .gitignore ├── pre-commit └── pre-push ├── .pypirc ├── Browser ├── __init__.py ├── assertion_engine.py ├── base │ ├── __init__.py │ ├── cache.py │ └── librarycomponent.py ├── browser.py ├── dev-requirements.txt ├── entry │ ├── __init__.py │ ├── __main__.py │ ├── constant.py │ ├── coverage_combine.py │ ├── get_versions.py │ ├── transform.py │ └── translation.py ├── gen_stub.py ├── keywords │ ├── __init__.py │ ├── assertion_formatter.py │ ├── browser_control.py │ ├── clock.py │ ├── cookie.py │ ├── coverage.py │ ├── crawling.py │ ├── device_descriptors.py │ ├── evaluation.py │ ├── getters.py │ ├── interaction.py │ ├── locator_handler.py │ ├── network.py │ ├── pdf.py │ ├── playwright_state.py │ ├── promises.py │ ├── runonfailure.py │ ├── strict_mode.py │ ├── waiter.py │ └── webapp_state.py ├── mypy.ini ├── playwright.py ├── pyproject.toml ├── requirements.txt ├── tidy_transformer │ ├── __init__.py │ └── network_idle.py ├── utils │ ├── __init__.py │ ├── data_types.py │ ├── deprecated.py │ ├── js_utilities.py │ ├── logger.py │ ├── meta_python.py │ ├── misc.py │ ├── robot_booleans.py │ └── settings_stack.py └── version.py ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CORE_TEAM_AGREEMENT.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── atest ├── atest_order.txt ├── library │ ├── certificate.py │ ├── common.py │ ├── os_wrapper.py │ ├── presenter_mode.py │ ├── same_keyword.py │ ├── screenshot.py │ └── show_trace_tool.py └── test │ ├── 00_Clean_Output_Dir │ ├── 01_initial_import.robot │ ├── 02_second_import.robot │ └── 03_import_by_keyword.robot │ ├── 01_Browser_Management │ ├── Auto_Closing │ │ ├── auto_closing.robot │ │ └── auto_closing_suite_level.robot │ ├── chromiun_channel.robot │ ├── client_certificates.robot │ ├── clock.robot │ ├── coverage.robot │ ├── coverageConfig.js │ ├── coverageConfigCombine.js │ ├── coverageConfigMD.js │ ├── device_descriptors.robot │ ├── element_selector.robot │ ├── geolocation.robot │ ├── get_empty_ids.robot │ ├── get_ids.robot │ ├── goto.robot │ ├── grpc_overflow.robot │ ├── hangs_setup.robot │ ├── imports.resource │ ├── keyword_banner.py │ ├── keyword_banner.robot │ ├── local_storage.robot │ ├── new_page_should_not_timeout.robot │ ├── no_httpCredentials_logging.robot │ ├── offline.robot │ ├── open_browser.robot │ ├── persistent_state.robot │ ├── playwright_state.robot │ ├── playwright_state_getters.robot │ ├── record_har.robot │ ├── reload.robot │ ├── reuse_existing_browsers.robot │ ├── run_on_failure.robot │ ├── storage_state.robot │ ├── switching_browsers.robot │ ├── timeout.robot │ ├── tracing.robot │ ├── tracing_groups.py │ ├── tracing_groups.robot │ └── video.robot │ ├── 02_Content_Keywords │ ├── __init__.robot │ ├── assertions.robot │ ├── basic_getters.robot │ ├── basic_getters_expect_errors.robot │ ├── checkbox.robot │ ├── click.robot │ ├── click_deprecated_options.robot │ ├── click_with_options.robot │ ├── cookie.robot │ ├── cookies_no_Browser.robot │ ├── dialogs.robot │ ├── files.robot │ ├── findLocator.robot │ ├── focus.robot │ ├── imports.resource │ ├── invalid_assertions.robot │ ├── invalid_test_upload_file │ ├── keyboardInput.robot │ ├── keypress.robot │ ├── keys │ │ ├── private_key.json │ │ └── public_key.key │ ├── locator_handler.robot │ ├── pdf.robot │ ├── readme_example.robot │ ├── screenshot.robot │ ├── scroll_by_and_to.robot │ ├── select_lists.robot │ ├── solve_draggame.robot │ ├── strict_mode.robot │ ├── styles.robot │ ├── tab.robot │ ├── tables.robot │ ├── test_record_selector.robot │ ├── test_upload_file │ ├── text_keywords.robot │ ├── title_should_be.robot │ ├── virtual_keyboard.robot │ └── virtual_mouse.robot │ ├── 03_Waiting │ ├── __init__.robot │ ├── imports.resource │ ├── promise_to.robot │ ├── wait_for_condition.robot │ ├── wait_for_element_state.robot │ ├── wait_for_function.robot │ ├── wait_for_http.robot │ └── wait_for_page_load_state.robot │ ├── 04_frames │ ├── deep_frames.robot │ ├── frames_and_elements.robot │ └── imports.resource │ ├── 05_JS_Tests │ ├── another.js │ ├── funky.js │ ├── http.robot │ ├── http_with_waiting.robot │ ├── imports.resource │ ├── jsextension.robot │ ├── jsextension_comma.robot │ ├── jsextension_list.robot │ ├── local_session_storage.robot │ ├── on_page_js.robot │ └── wrong.js │ ├── 06_Examples │ ├── crawling.robot │ ├── imports.resource │ ├── js_evaluation.robot │ ├── open_in_another_tab.robot │ ├── presenter_mode.robot │ ├── promised_cat_and_dog.robot │ └── webcomponent.robot │ ├── 07_Failing │ ├── custom_failure_screenshot.robot │ ├── imports.resource │ └── screenshot_on_failure.robot │ ├── 08_Scope_Tests │ ├── Suite_1 │ │ ├── Suite_1.1 │ │ │ ├── Suite_1.1.1.robot │ │ │ ├── Suite_1.1.2.robot │ │ │ └── Suite_1.1.3.robot │ │ └── Suite_1.2 │ │ │ └── Suite_1.2.1.robot │ ├── Suite_2 │ │ ├── Suite_2.1 │ │ │ └── Suite_2.1.1.robot │ │ └── __init__.robot │ ├── __init__.robot │ ├── scope_keywords.resource │ └── scope_logger.py │ ├── 09_Plugins │ ├── ExamplePlugin.py │ ├── Import_JS_and_JS_in_Python.robot │ ├── imports.resource │ ├── jsplugin.js │ └── plugin.robot │ ├── 10_retest │ ├── Browser_New_Context_Test.robot │ └── mylib.py │ ├── 11_tidy_transformer │ ├── imports.resource │ ├── network_idle_file.robot │ ├── network_idle_test.robot │ └── readme.md │ ├── 12_rfbrowser │ ├── SomePlugin.py │ ├── imports.resource │ ├── jsplugin.js │ └── translation.robot │ ├── __init__.robot │ ├── keywords.resource │ └── variables.resource ├── bootstrap.py ├── browser_lib_logo.svg ├── docker ├── Dockerfile.latest_release ├── Dockerfile.tests ├── README.md ├── atest │ └── test.robot └── seccomp_profile.json ├── docs ├── examples │ └── babelES2015 │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── lib │ │ └── index.js │ │ ├── package │ │ ├── package-lock.json │ │ ├── package.json │ │ └── src │ │ └── index.js ├── plugins │ ├── README.md │ └── example │ │ ├── ExamplePlugin.py │ │ └── test.robot ├── releasenotes │ ├── Browser-1.4.0.rst │ ├── Browser-1.5.0.rst │ ├── Browser-1.6.0.rst │ ├── Browser-1.7.0.rst │ ├── Browser-10.0.0.rst │ ├── Browser-10.0.1.rst │ ├── Browser-10.0.2.rst │ ├── Browser-10.0.3.rst │ ├── Browser-10.1.0.rst │ ├── Browser-11.0.0.rst │ ├── Browser-11.1.0.rst │ ├── Browser-11.1.1.rst │ ├── Browser-11.2.0.rst │ ├── Browser-11.3.0.rst │ ├── Browser-11.4.0.rst │ ├── Browser-12.0.0.rst │ ├── Browser-12.0.1.rst │ ├── Browser-12.1.0.rst │ ├── Browser-12.2.0.rst │ ├── Browser-12.3.0.rst │ ├── Browser-12.4.0.rst │ ├── Browser-13.0.0.rst │ ├── Browser-13.1.0.rst │ ├── Browser-13.2.0.rst │ ├── Browser-13.3.0.rst │ ├── Browser-13.4.0.rst │ ├── Browser-13.5.0.rst │ ├── Browser-14.0.0.rst │ ├── Browser-14.1.0.rst │ ├── Browser-14.2.0.rst │ ├── Browser-14.2.1.rst │ ├── Browser-14.3.0.rst │ ├── Browser-14.4.0.rst │ ├── Browser-14.4.1.rst │ ├── Browser-15.0.0.rst │ ├── Browser-15.0.1.rst │ ├── Browser-15.1.0.rst │ ├── Browser-15.2.0.rst │ ├── Browser-16.0.0.rst │ ├── Browser-16.0.1.rst │ ├── Browser-16.0.2.rst │ ├── Browser-16.0.3.rst │ ├── Browser-16.0.4.rst │ ├── Browser-16.0.5.rst │ ├── Browser-16.1.0.rst │ ├── Browser-16.2.0.rst │ ├── Browser-16.3.0.rst │ ├── Browser-17.0.0.rst │ ├── Browser-17.1.0.rst │ ├── Browser-17.1.1.rst │ ├── Browser-17.2.0.rst │ ├── Browser-17.3.0.rst │ ├── Browser-17.4.0.rst │ ├── Browser-17.5.0.rst │ ├── Browser-17.5.1.rst │ ├── Browser-17.5.2.rst │ ├── Browser-18.0.0.rst │ ├── Browser-18.1.0.rst │ ├── Browser-18.2.0.rst │ ├── Browser-18.3.0.rst │ ├── Browser-18.4.0.rst │ ├── Browser-18.5.0.rst │ ├── Browser-18.5.1.rst │ ├── Browser-18.6.0.rst │ ├── Browser-18.6.1.rst │ ├── Browser-18.6.2.rst │ ├── Browser-18.6.3.rst │ ├── Browser-18.7.0.rst │ ├── Browser-18.8.0.rst │ ├── Browser-18.8.1.rst │ ├── Browser-18.9.0.rst │ ├── Browser-18.9.1.rst │ ├── Browser-19.0.0.rst │ ├── Browser-19.0.1.rst │ ├── Browser-19.1.0.rst │ ├── Browser-19.1.1.rst │ ├── Browser-19.1.2.rst │ ├── Browser-19.2.0.rst │ ├── Browser-19.3.0.rst │ ├── Browser-19.3.1.rst │ ├── Browser-19.4.0.rst │ ├── Browser-19.5.0.rst │ ├── Browser-19.5.1.rst │ ├── Browser-2.0.0.rst │ ├── Browser-2.1.0.rst │ ├── Browser-2.2.0.rst │ ├── Browser-2.3.1.rst │ ├── Browser-2.3.4.rst │ ├── Browser-2.4.0.rst │ ├── Browser-2.4.1.rst │ ├── Browser-2.5.0.rst │ ├── Browser-3.0.0.rst │ ├── Browser-3.1.0.rst │ ├── Browser-3.1.1.rst │ ├── Browser-3.3.0.rst │ ├── Browser-3.3.2.rst │ ├── Browser-4.0.1.rst │ ├── Browser-4.1.0.rst │ ├── Browser-4.2.0.rst │ ├── Browser-4.3.0.rst │ ├── Browser-4.4.0.rst │ ├── Browser-4.5.0.rst │ ├── Browser-5.0.0.rst │ ├── Browser-5.0.1.rst │ ├── Browser-5.1.0.rst │ ├── Browser-5.1.1.rst │ ├── Browser-5.1.2.rst │ ├── Browser-5.2.0.rst │ ├── Browser-6.0.0.rst │ ├── Browser-7.0.0.rst │ ├── Browser-7.1.0.rst │ ├── Browser-7.1.1.rst │ ├── Browser-7.2.0.rst │ ├── Browser-8.0.0.rst │ ├── Browser-8.0.1.rst │ ├── Browser-9.0.0.rst │ ├── Browser-9.0.1.rst │ └── Browser-9.0.2.rst ├── style.css └── versions │ ├── Browser-0.15.1.html │ ├── Browser-0.16.0.html │ ├── Browser-1.0.0.html │ ├── Browser-1.1.1.html │ ├── Browser-1.2.3.html │ ├── Browser-1.2.4.html │ ├── Browser-1.3.0-dev.html │ ├── Browser-1.3.0.html │ ├── Browser-1.4.0.html │ ├── Browser-1.5.0.html │ ├── Browser-1.6.0.html │ ├── Browser-1.7.0.html │ ├── Browser-10.0.0.html │ ├── Browser-10.0.1.html │ ├── Browser-10.0.2.html │ ├── Browser-10.1.0.html │ ├── Browser-11.1.1.html │ ├── Browser-11.2.0.html │ ├── Browser-11.3.0.html │ ├── Browser-11.4.0.html │ ├── Browser-12.0.0.html │ ├── Browser-12.0.1.html │ ├── Browser-12.1.0.html │ ├── Browser-12.2.0.html │ ├── Browser-12.3.0.html │ ├── Browser-12.4.0.html │ ├── Browser-13.0.0.html │ ├── Browser-13.1.0.html │ ├── Browser-13.2.0.html │ ├── Browser-13.3.0.html │ ├── Browser-13.4.0.html │ ├── Browser-13.6.0.html │ ├── Browser-14.0.0.html │ ├── Browser-14.0.1.html │ ├── Browser-14.1.0.html │ ├── Browser-14.2.0.html │ ├── Browser-14.2.1.html │ ├── Browser-14.3.0.html │ ├── Browser-14.4.0.html │ ├── Browser-15.0.0.html │ ├── Browser-15.0.1.html │ ├── Browser-15.1.0.html │ ├── Browser-15.2.0.html │ ├── Browser-16.0.0.html │ ├── Browser-16.0.1.html │ ├── Browser-16.0.2.html │ ├── Browser-16.0.3.html │ ├── Browser-16.0.4.html │ ├── Browser-16.0.5.html │ ├── Browser-16.1.0.html │ ├── Browser-16.2.0.html │ ├── Browser-16.3.0.html │ ├── Browser-17.0.0.html │ ├── Browser-17.1.0.html │ ├── Browser-17.1.1.html │ ├── Browser-17.2.0.html │ ├── Browser-17.3.0.html │ ├── Browser-17.4.0.html │ ├── Browser-17.5.0.html │ ├── Browser-17.5.1.html │ ├── Browser-17.5.2.html │ ├── Browser-18.0.0.html │ ├── Browser-18.1.0.html │ ├── Browser-18.2.0.html │ ├── Browser-18.3.0.html │ ├── Browser-18.4.0.html │ ├── Browser-18.5.0.html │ ├── Browser-18.5.1.html │ ├── Browser-18.6.0.html │ ├── Browser-18.6.1.html │ ├── Browser-18.6.2.html │ ├── Browser-18.6.3.html │ ├── Browser-18.7.0.html │ ├── Browser-18.8.0.html │ ├── Browser-18.8.1.html │ ├── Browser-18.9.0.html │ ├── Browser-18.9.1.html │ ├── Browser-19.0.0.html │ ├── Browser-19.0.1.html │ ├── Browser-19.1.0.html │ ├── Browser-19.1.1.html │ ├── Browser-19.1.2.html │ ├── Browser-19.2.0.html │ ├── Browser-19.3.0.html │ ├── Browser-19.3.1.html │ ├── Browser-19.4.0.html │ ├── Browser-19.5.0.html │ ├── Browser-19.5.1.html │ ├── Browser-2.0.0.html │ ├── Browser-2.1.0.html │ ├── Browser-2.3.0.html │ ├── Browser-2.3.1.html │ ├── Browser-2.3.2.html │ ├── Browser-2.3.3.html │ ├── Browser-2.3.4.html │ ├── Browser-2.4.0.html │ ├── Browser-2.4.1.html │ ├── Browser-2.4.2.html │ ├── Browser-2.5.0.html │ ├── Browser-3.0.0.html │ ├── Browser-3.0.1.html │ ├── Browser-3.1.0.html │ ├── Browser-3.1.1.html │ ├── Browser-3.2.0.html │ ├── Browser-3.3.0.html │ ├── Browser-3.3.1.html │ ├── Browser-3.3.2.html │ ├── Browser-4.0.0.html │ ├── Browser-4.0.1.html │ ├── Browser-4.1.0.html │ ├── Browser-4.2.0.html │ ├── Browser-4.3.0.html │ ├── Browser-4.4.0.html │ ├── Browser-4.5.0.html │ ├── Browser-4.5.1.html │ ├── Browser-4.5.2.html │ ├── Browser-5.0.0.html │ ├── Browser-5.0.1.html │ ├── Browser-5.1.0.html │ ├── Browser-5.1.1.html │ ├── Browser-5.1.2.html │ ├── Browser-5.2.0.html │ ├── Browser-6.0.0.html │ ├── Browser-7.0.0.html │ ├── Browser-7.1.0.html │ ├── Browser-7.1.1.html │ ├── Browser-7.2.0.html │ ├── Browser-8.0.0.html │ ├── Browser-8.0.1.html │ ├── Browser-8.0.2.html │ ├── Browser-9.0.0.html │ ├── Browser-9.0.1.html │ └── Browser-9.0.2.html ├── node ├── .prettierrc.js ├── build.testapp.js ├── build.wrapper.js ├── dynamic-test-app │ ├── src │ │ ├── app.tsx │ │ ├── draggame.tsx │ │ ├── index.tsx │ │ ├── login.tsx │ │ └── server.ts │ └── static │ │ ├── Moving Robot Framework browser automation to 2020 (or 2021).pdf │ │ ├── clock.html │ │ ├── delayed-load.html │ │ ├── demo.css │ │ ├── dialogs.html │ │ ├── dogandcat.html │ │ ├── enabled_disabled_fields_form.html │ │ ├── error.html │ │ ├── frames │ │ ├── bar.html │ │ ├── deep │ │ │ ├── a.html │ │ │ ├── b.html │ │ │ └── c.html │ │ ├── foo.html │ │ ├── frame_for_element_frame.html │ │ ├── frame_for_elements.html │ │ ├── frameset.html │ │ ├── iframes.html │ │ ├── left.html │ │ ├── poorlynamedframe.html │ │ ├── right.html │ │ └── search_results.html │ │ ├── framing.html │ │ ├── geolocation.html │ │ ├── index.html │ │ ├── links │ │ ├── always_linked.html │ │ ├── link1.html │ │ ├── link2.html │ │ ├── link3.html │ │ └── link4.html │ │ ├── overlay.html │ │ ├── popup.html │ │ ├── popups │ │ ├── call_popup.html │ │ ├── first_popup.html │ │ └── second_popup.html │ │ ├── postredirect.html │ │ ├── prefilled_email_form.html │ │ ├── robot-framework.svg │ │ ├── scrolling.html │ │ ├── shell_game.html │ │ ├── spaces.html │ │ ├── tables.html │ │ ├── two_dialogs.html │ │ ├── waitfor.html │ │ ├── webcomponent.html │ │ └── welcome.html ├── eslint.config.mjs ├── playwright-wrapper │ ├── browser-control.ts │ ├── clock.ts │ ├── cookie.ts │ ├── device-descriptors.ts │ ├── evaluation.ts │ ├── getters.ts │ ├── grpc-service.ts │ ├── index.ts │ ├── interaction.ts │ ├── keyword-decorators.ts │ ├── locator-handler.ts │ ├── network.ts │ ├── pdf.ts │ ├── playwright-invoke.ts │ ├── playwright-state.ts │ ├── response-util.ts │ └── static │ │ └── selector-finder.js └── tsconfig.json ├── package-lock.json ├── package.json ├── protobuf └── playwright.proto ├── setup.py ├── tasks.py └── utest ├── __init__.py ├── approvaltests_config.json ├── approved_files ├── test_context_cache.test_add_cache.approved.txt ├── test_context_cache.test_cache_is_empty.approved.txt ├── test_context_cache.test_get_item.approved.txt ├── test_context_cache.test_get_item_no_item.approved.txt ├── test_context_cache.test_remove_item.approved.txt ├── test_context_cache.test_remove_item_no_item.approved.txt ├── test_get_video_size.test_get_video_size.approved.txt ├── test_presenter_mode_conversion.test_presenter_mode_default.approved.txt ├── test_presenter_mode_conversion.test_presenter_mode_duration_as_string.approved.txt ├── test_presenter_mode_conversion.test_presenter_mode_duration_as_timedelta.approved.txt ├── test_rfbrowser_translate.test_body_line.approved.txt ├── test_rfbrowser_translate.test_full_long_kw_table.approved.txt ├── test_rfbrowser_translate.test_heading.approved.txt └── test_type_converter.test_type_converter.approved.txt ├── conftest.py ├── custom_locator_handler.js ├── robotframework_browser_translation_as_list ├── __init__.py ├── translate_1.json └── translate_2.json ├── robotframework_browser_translation_fi ├── __init__.py └── translate.json ├── robotframework_browser_translation_not_working ├── __init__.py └── translate.json ├── test_browser_folder_cleanup.py ├── test_context_cache.py ├── test_cookie.py ├── test_deprecated.py ├── test_development_functionality.py ├── test_docs.py ├── test_format_cookie.py ├── test_get_normalized_keyword.py ├── test_get_text.py ├── test_get_time.py ├── test_get_video_size.py ├── test_network.py ├── test_output_dir.py ├── test_presenter_mode_conversion.py ├── test_python_usage.py ├── test_rfbrowser_translate.py ├── test_run_on_failure.py ├── test_screenshot.py ├── test_secrets.py ├── test_shared_playwright_port.py ├── test_translation.py ├── test_type_converter.py └── test_waiters.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | .venv/* 4 | utest/* 5 | atest/* 6 | tasks.py 7 | Browser/generated/* 8 | 9 | [report] 10 | skip_empty=True 11 | skip_covered=True 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | *Dockerfile* 3 | *docker-compose* 4 | node_modules 5 | .mypy_cache 6 | .pytest_cache 7 | .github 8 | .idea 9 | .venv 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *_pb2.py eol=lf 2 | *_pb*.[tj]s eol=lf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | groups: 8 | eslint: 9 | patterns: 10 | - "@typescript-eslint*" 11 | 12 | - package-ecosystem: "pip" 13 | directory: "/Browser" 14 | schedule: 15 | interval: "daily" 16 | groups: 17 | grpc: 18 | patterns: 19 | - "grpcio*" 20 | - "protobu*" 21 | 22 | - package-ecosystem: "github-actions" 23 | directory: "/" 24 | schedule: 25 | interval: "daily" 26 | -------------------------------------------------------------------------------- /.github/workflows/on-release.yml: -------------------------------------------------------------------------------- 1 | name: Release tasks 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | gh-pages: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | persist-credentials: false 14 | - name: Use Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: 22.x 18 | - name: Set up Python 3.12 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: 3.12 22 | - name: Dependencies for building docs 23 | run: | 24 | npm ci 25 | python -m pip install --upgrade pip 26 | pip install uv 27 | uv pip install -r Browser/dev-requirements.txt --python 3.12 --system 28 | inv build 29 | - name: Build docs 30 | run: | 31 | inv docs 32 | inv gh-pages-index 33 | - name: Deploy 🚀 34 | uses: JamesIves/github-pages-deploy-action@v4.7.3 35 | with: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | BRANCH: gh-pages # The branch the action should deploy to. 38 | FOLDER: docs # The folder the action should deploy. 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Browser/wrapper/*.js 2 | Browser/wrapper 3 | Browser/generated 4 | Browser/__init__.pyi 5 | Browser/browser.pyi 6 | Browser/**/*.pyc 7 | Browser/rfbrowser.log 8 | node/playwright-wrapper/generated 9 | 10 | docs/Browser.html 11 | docs/index.html 12 | 13 | node/dynamic-test-app/dist/*.js 14 | node/dynamic-test-app/src/*.js 15 | *.js.map 16 | 17 | .idea 18 | .pabotsuitenames 19 | .mypy_cache 20 | __pycache__ 21 | robotframework_browser.egg-info/ 22 | .venv 23 | zip_results 24 | 25 | # utest 26 | utest/output 27 | .pytest_cache 28 | flip_rate 29 | 30 | node/.linted 31 | node/.built 32 | node/dynamic-test-app/.built 33 | Browser/.linted 34 | Browser/.installed 35 | 36 | # Robot Framework outputs 37 | log.html 38 | output.xml 39 | report.html 40 | atest/output 41 | playwright-log.txt 42 | lsp 43 | 44 | node_modules/ 45 | dist 46 | build 47 | pip-wheel-metadata 48 | tmp 49 | .vscode 50 | 51 | # coverage 52 | .coverage 53 | htmlcov 54 | results 55 | 56 | # linting 57 | .ruff_cache 58 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: | 3 | pip install robotframework 4 | command: | 5 | python -m pip install --upgrade pip 6 | pip install -r Browser/dev-requirements.txt 7 | inv deps --system 8 | npx --yes playwright install-deps 9 | inv build 10 | image: gitpod/workspace-full-vnc 11 | ports: 12 | - port: 6080 13 | onOpen: open-preview 14 | vscode: 15 | extensions: 16 | - ms-python.python 17 | - d-biehl.robotcode 18 | - eamodio.gitlens 19 | - dbaeumer.vscode-eslint 20 | - ms-python.debugpy 21 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | echo 'precommit 🔱' && inv lint 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | echo 'prepush 🚀' && inv build && inv utest && inv atest 5 | -------------------------------------------------------------------------------- /.pypirc: -------------------------------------------------------------------------------- 1 | [distutils] 2 | index-servers = 3 | pypi 4 | testpypi 5 | 6 | [pypi] 7 | repository: https://upload.pypi.org/legacy/ 8 | 9 | [testpypi] 10 | https://test.pypi.org/legacy/ 11 | -------------------------------------------------------------------------------- /Browser/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from assertionengine import AssertionOperator 15 | 16 | from .browser import Browser 17 | from .utils.data_types import ( 18 | BoundingBox, 19 | ColorScheme, 20 | DialogAction, 21 | ElementState, 22 | GeoLocation, 23 | KeyboardModifier, 24 | MouseButton, 25 | RecordHar, 26 | RecordVideo, 27 | RequestMethod, 28 | SelectAttribute, 29 | SupportedBrowsers, 30 | ViewportDimensions, 31 | ) 32 | from .version import __version__ as VERSION 33 | 34 | __version__ = VERSION 35 | __all__ = [ 36 | "AssertionOperator", 37 | "BoundingBox", 38 | "Browser", 39 | "ColorScheme", 40 | "DialogAction", 41 | "ElementState", 42 | "GeoLocation", 43 | "KeyboardModifier", 44 | "MouseButton", 45 | "RecordHar", 46 | "RecordVideo", 47 | "RequestMethod", 48 | "SelectAttribute", 49 | "SupportedBrowsers", 50 | "ViewportDimensions", 51 | ] 52 | -------------------------------------------------------------------------------- /Browser/base/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from Browser.base.cache import ContextCache # noqa 16 | from Browser.base.librarycomponent import LibraryComponent # noqa 17 | -------------------------------------------------------------------------------- /Browser/base/cache.py: -------------------------------------------------------------------------------- 1 | class ContextCache: 2 | def __init__(self): 3 | self.cache = {} 4 | 5 | def add(self, cache_uuid: str, item: dict): 6 | self.cache[cache_uuid] = item 7 | 8 | def remove(self, cache_uuid: str): 9 | self.cache.pop(cache_uuid, None) 10 | 11 | def get(self, cache_uuid: str): 12 | return self.cache.get(cache_uuid, None) 13 | -------------------------------------------------------------------------------- /Browser/dev-requirements.txt: -------------------------------------------------------------------------------- 1 | invoke >= 2.2.0 2 | pytest >= 7.4.2 3 | pytest-watch >= 4.2.0 4 | mypy >= 1.8.0 5 | mypy-protobuf >= 3.5.0 6 | ruff>=0.9.7 7 | wheel>=0.40.0 8 | robotframework-pabot >= 4.0.0 9 | twine >= 4.0.2 10 | robotstatuschecker >= 4.1.1 11 | rellu >= 0.7 12 | approvaltests == 14.5.0 13 | pytest-mock==3.14.1 14 | Pillow==11.2.1 15 | # Required for injecting google analytics tags on release 16 | beautifulsoup4 >= 4.12.2 17 | psutil >= 5.9.4 18 | coverage >= 7.2.3 19 | robotframework-tidy >= 4.9.0 20 | python-dateutil >= 2.8.2 21 | robotframework-crypto >= 0.3.0 22 | robotframework-archivelibrary >= 0.4.3 23 | uv >= 0.6.2 24 | build >= 1.2.2 25 | # Include normal dependencies from requirements.txt. Makes it possible to use 26 | # requirements-dev.txt as a single requirement file in PyCharm and other IDEs. 27 | -r requirements.txt 28 | -------------------------------------------------------------------------------- /Browser/entry/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /Browser/entry/transform.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import subprocess 16 | from pathlib import Path 17 | 18 | TIDY_TRANSFORMER_DIR = Path(__file__).parent.parent / "tidy_transformer" 19 | 20 | 21 | def transform( 22 | path: Path, 23 | wait_until_network_is_idle: bool, 24 | ) -> None: 25 | cmd = ["robotidy"] 26 | if wait_until_network_is_idle: 27 | wait_until_network_is_idle_file = TIDY_TRANSFORMER_DIR / "network_idle.py" 28 | cmd.append("--transform") 29 | cmd.append(str(wait_until_network_is_idle_file)) 30 | cmd.extend([str(item) for item in path]) # type: ignore 31 | subprocess.run(cmd, check=False) 32 | -------------------------------------------------------------------------------- /Browser/keywords/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from .assertion_formatter import Formatter 16 | from .browser_control import Control 17 | from .clock import Clock 18 | from .cookie import Cookie 19 | from .coverage import Coverage 20 | from .device_descriptors import Devices 21 | from .evaluation import Evaluation 22 | from .getters import Getters 23 | from .interaction import Interaction 24 | from .locator_handler import LocatorHandler 25 | from .network import Network 26 | from .pdf import Pdf 27 | from .playwright_state import PlaywrightState 28 | from .promises import Promises 29 | from .runonfailure import RunOnFailureKeywords 30 | from .strict_mode import StrictMode 31 | from .waiter import Waiter 32 | from .webapp_state import WebAppState 33 | 34 | __all__ = [ 35 | "Clock", 36 | "Control", 37 | "Cookie", 38 | "Coverage", 39 | "Devices", 40 | "Evaluation", 41 | "Formatter", 42 | "Getters", 43 | "Interaction", 44 | "LocatorHandler", 45 | "Network", 46 | "Pdf", 47 | "PlaywrightState", 48 | "Promises", 49 | "RunOnFailureKeywords", 50 | "StrictMode", 51 | "Waiter", 52 | "WebAppState", 53 | ] 54 | -------------------------------------------------------------------------------- /Browser/keywords/strict_mode.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from ..base import LibraryComponent 15 | from ..utils import Scope, keyword, logger 16 | 17 | 18 | class StrictMode(LibraryComponent): 19 | @keyword(tags=("Setter", "BrowserControl")) 20 | def set_strict_mode(self, mode: bool, scope: Scope = Scope.Suite): 21 | """Controls library strict mode. 22 | 23 | | =Arguments= | =Description= | 24 | | ``mode`` | When set to ``True``, keywords that are searching elements will use Playwright [https://playwright.dev/docs/api/class-page#page-query-selector|strict mode]. Keyword changes library strict mode value and keyword also return the previous strict mode value. | 25 | | ``scope`` | Scope defines the live time of that setting. Available values are ``Global``, ``Suite`` or ``Test`` / ``Task``. See `Scope` for more details. | 26 | 27 | 28 | Example: 29 | | ${old_mode} = Set Strict Mode False 30 | | Get Text //input # Does not fail if selector points to one or more elements 31 | | Set Strict Mode ${old_mode} 32 | 33 | [https://forum.robotframework.org/t//4332|Comment >>] 34 | """ 35 | old_mode = self.strict_mode 36 | self.strict_mode_stack.set(mode, scope) 37 | logger.debug(f"Old mode was {old_mode}") 38 | return old_mode 39 | -------------------------------------------------------------------------------- /Browser/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version = 3.9 3 | warn_unused_ignores = True 4 | no_implicit_optional = True 5 | check_untyped_defs = True 6 | 7 | [mypy-Browser.generated.*] 8 | ignore_errors = True 9 | ignore_missing_imports = True 10 | 11 | [mypy-robot.*] 12 | ignore_missing_imports = True 13 | 14 | [mypy-pytest.*] 15 | ignore_missing_imports = True 16 | -------------------------------------------------------------------------------- /Browser/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | lint.unfixable = [] 3 | exclude = [ 4 | "__pycache__", 5 | "generated", 6 | "wrapper", 7 | "browser.pyi", 8 | ] 9 | lint.ignore = [ 10 | "B008", # do not perform function calls in argument defaults 11 | "B904", # TODO only temporary. should be fixed. 12 | "E501", # line too long 13 | "N815", # mixedCase variable in class scope 14 | "N803", # argument name should be lowercase 15 | "N806", # variable in function should be lowercase 16 | "N812", # lowercase imported as non lowercase 17 | "N999", # Invalid module name: 'Browser' 18 | "PLR0913", # too many arguments 19 | ] 20 | target-version = "py39" 21 | lint.select = [ 22 | "E", 23 | "F", 24 | "W", 25 | "C90", 26 | "I", 27 | "N", 28 | "B", 29 | "PYI", 30 | "PL", 31 | "PTH", 32 | "UP", 33 | "A", 34 | "C4", 35 | "DTZ", 36 | "ISC", 37 | "ICN", 38 | "INP", 39 | "PIE", 40 | "T20", 41 | "PYI", 42 | "PT", 43 | "RSE", 44 | "RET", 45 | "SIM", 46 | "RUF" 47 | ] 48 | [tool.ruff.lint.per-file-ignores] 49 | "tasks.py" = [ 50 | "T201", 51 | "PTH123", 52 | "PTH120" 53 | ] 54 | "bootstrap.py" = ["T201"] 55 | 56 | [tool.robotidy] 57 | src = ["atest"] 58 | lineseparator = "unix" 59 | configure = [ 60 | "NormalizeAssignments:equal_sign_type=space_and_equal_sign", 61 | "NormalizeAssignments:equal_sign_type_variables=space_and_equal_sign", 62 | "NormalizeNewLines:section_lines=1", 63 | "RenameKeywords:enabled=True", 64 | "RenameTestCases:capitalize_each_word=True:enabled=True" 65 | ] 66 | -------------------------------------------------------------------------------- /Browser/requirements.txt: -------------------------------------------------------------------------------- 1 | grpcio == 1.72.1 2 | grpcio-tools == 1.72.1 3 | protobuf == 6.31.1 4 | robotframework >= 5.0.1, < 8.0.0 5 | robotframework-pythonlibcore >= 4.4.1, < 5.0.0 6 | robotframework-assertion-engine >= 3.0.3, < 4.0.0 7 | wrapt >= 1.15.0 8 | overrides >= 7.3.1 9 | click >= 8.1.7 10 | seedir >= 0.5.0 11 | -------------------------------------------------------------------------------- /Browser/tidy_transformer/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /Browser/utils/js_utilities.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import re 16 | from typing import Any 17 | 18 | 19 | def get_abs_scroll_coordinates( 20 | query: Any, scroll_size: int, min_str: str, max_str: str 21 | ): 22 | try: 23 | return round(float(query)) 24 | except ValueError: 25 | pass 26 | if isinstance(query, str): 27 | m = re.search(f"^(?:({min_str})|({max_str}))$", query, re.IGNORECASE) 28 | if m: 29 | return 0 if m.group(1) else scroll_size 30 | m = re.search(r"^(\d+(?:.\d+)?)%$", query) 31 | if m: 32 | return (scroll_size * float(m.group(1))) // 100 33 | raise ValueError( 34 | f"Argument must be positive int, percentage or string <{min_str}|{max_str}> but was {type(query)} with value `{query}`." 35 | ) 36 | 37 | 38 | def get_rel_scroll_coordinates(query: Any, full: int, client: int, dimension_str: str): 39 | try: 40 | return round(float(query)) 41 | except ValueError: 42 | pass 43 | if isinstance(query, str): 44 | m = re.search(f"^([-+]?)({dimension_str})$", query, re.IGNORECASE) 45 | if m: 46 | return int(f"{m.group(1)}{client}") 47 | m = re.search(r"^([+-]?\d+(?:.\d+)?)%$", query) 48 | if m: 49 | return (full * float(m.group(1))) // 100 50 | raise ValueError( 51 | f"Argument must be int, percentage or string but was {type(query)} with value `{query}`." 52 | ) 53 | -------------------------------------------------------------------------------- /Browser/utils/meta_python.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from enum import Enum 15 | from typing import Any, TypeVar 16 | 17 | 18 | def locals_to_params(args: dict) -> dict: 19 | copy: dict[str, Any] = {} 20 | for key, value in args.items(): 21 | if key == "self": 22 | continue 23 | if value is not None: 24 | if isinstance(value, Enum): 25 | copy[key] = value.name 26 | elif isinstance(value, list): 27 | copy[key] = [ 28 | item.name if isinstance(item, Enum) else item for item in value 29 | ] 30 | else: 31 | copy[key] = value 32 | return copy 33 | 34 | 35 | """ Finds first dict in list of dicts containing field id with value equal to id""" 36 | T = TypeVar("T") 37 | 38 | 39 | def find_by_id(_id: str, item_list: list[dict[str, T]], log_error=True) -> dict[str, T]: 40 | from ..utils import logger 41 | 42 | def filter_fn(item): 43 | return item["id"] == _id 44 | 45 | try: 46 | filtered = filter(filter_fn, item_list) 47 | return next(filtered) 48 | except StopIteration: 49 | if log_error: 50 | logger.error( 51 | f"No item with correct id {_id}. Existing ids: {[item['id'] for item in item_list]}" 52 | ) 53 | raise 54 | -------------------------------------------------------------------------------- /Browser/utils/robot_booleans.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020- Robot Framework Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Any 16 | 17 | TRUE_STRINGS = {"TRUE", "YES", "ON", "1", "CHECKED"} 18 | FALSE_STRINGS = {"FALSE", "NO", "OFF", "0", "UNCHECKED", "NONE", ""} 19 | 20 | 21 | def is_truthy(item: Any) -> bool: 22 | if isinstance(item, bool): 23 | return item 24 | if isinstance(item, str): 25 | return item.upper() not in FALSE_STRINGS 26 | return bool(item) 27 | 28 | 29 | def is_falsy(item: Any) -> bool: 30 | return item is None or not is_truthy(item) 31 | -------------------------------------------------------------------------------- /Browser/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "19.5.1" 2 | -------------------------------------------------------------------------------- /CORE_TEAM_AGREEMENT.md: -------------------------------------------------------------------------------- 1 | # Core team 2 | 3 | Core team membership gives a team member rights to release new versions 4 | of Robot Framework Browser. 5 | 6 | ## Current core team 7 | 8 | - Mikko Korpela 9 | - Tatu Aalto 10 | - Kerkko Pelttari 11 | - René Rohner 12 | 13 | ## Rules 14 | 15 | Default method of decision making is consent. 16 | This means that in the absense of objections 17 | a decision is made. 18 | 19 | Any one of the core team members can raise an objection. 20 | 21 | If an objection is raised, we agree a time and place to 22 | discuss the matter in an online meeting. 23 | If no consensus is achieved we will vote. 24 | Each team member gets one vote. Majority wins the vote. 25 | Vote can be made public if one of the team members requests it. 26 | 27 | ## Adding a new team member 28 | 29 | A new member to the team requires that all current team members support this. 30 | 31 | ## Removing a team member 32 | 33 | Anyone can leave the team by their own will. 34 | If all others from the core team agree that a team member should be removed from the team 35 | then that team member will no longer be part of the core team. 36 | 37 | ## Signed by: 38 | 39 | We agree on all of this by commiting a signed commit `git commit -S` on this file 40 | that adds signers name here: 41 | - Mikko Korpela 42 | - René Rohner 43 | - Kerkko Pelttari 44 | - Tatu Aalto 45 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | -------------------------------------------------------------------------------- /atest/atest_order.txt: -------------------------------------------------------------------------------- 1 | --suite Test.00 Clean Output Dir 2 | --suite Test.08 Scope Tests -------------------------------------------------------------------------------- /atest/library/presenter_mode.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from robot.libraries.BuiltIn import BuiltIn 4 | 5 | 6 | def set_presenter_mode(status: Union[dict, bool] = False): 7 | """Set presenter mode in Browser library""" 8 | browser = BuiltIn().get_library_instance("browser") 9 | print(f"Set presenter_mode to {status}") 10 | browser.presenter_mode = status 11 | -------------------------------------------------------------------------------- /atest/library/same_keyword.py: -------------------------------------------------------------------------------- 1 | def get_title(): 2 | """Dummy keyword to test Wait For Condition keyword""" 3 | return "111" 4 | -------------------------------------------------------------------------------- /atest/test/00_Clean_Output_Dir/01_initial_import.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Browser retry_assertions_for=2 sec 3 | Library OperatingSystem 4 | Resource ../variables.resource 5 | 6 | *** Test Cases *** 7 | Test Lazy Playwright Loading 8 | [Documentation] Tests that Playwright is not loaded until the first keyword is called 9 | ${browser_lib} = Get Library Instance Browser 10 | Should Be True $browser_lib._playwright is None 11 | ${cat} = Get Browser Catalog 12 | Should Be True isinstance($browser_lib._playwright, Browser.playwright.Playwright) 13 | 14 | Take Screenshot 15 | New Page ${TABLES_URL} 16 | File Should Not Exist ${OUTPUT DIR}/browser/screenshot/initial_screenshot.png 17 | File Should Not Exist ${OUTPUT DIR}/browser/screenshot/second_screenshot.png 18 | File Should Not Exist ${OUTPUT DIR}/browser/screenshot/third_screenshot.png 19 | ${initial_screenshot} = Take Screenshot initial_screenshot fullPage=True 20 | Set Global Variable ${initial_screenshot} 21 | File Should Exist ${initial_screenshot} 22 | [Teardown] Close Browser ALL 23 | -------------------------------------------------------------------------------- /atest/test/00_Clean_Output_Dir/02_second_import.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Browser retry_assertions_for=4 sec 3 | Library OperatingSystem 4 | Resource ../variables.resource 5 | 6 | *** Test Cases *** 7 | Take Screenshot 8 | New Page ${TABLES_URL} 9 | ${second_screenshot} = Take Screenshot second_screenshot fullPage=True 10 | Set Global Variable ${second_screenshot} 11 | File Should Exist ${second_screenshot} 12 | File Should Exist ${initial_screenshot} 13 | [Teardown] Close Browser ALL 14 | -------------------------------------------------------------------------------- /atest/test/00_Clean_Output_Dir/03_import_by_keyword.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library OperatingSystem 3 | Resource ../variables.resource 4 | 5 | *** Test Cases *** 6 | Take Screenshot 7 | Import Library Browser strict=${False} 8 | New Page ${TABLES_URL} 9 | ${third_screenshot} = Take Screenshot third_screenshot fullPage=True 10 | File Should Exist ${third_screenshot} 11 | File Should Exist ${initial_screenshot} 12 | File Should Exist ${second_screenshot} 13 | [Teardown] Close Browser ALL 14 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/Auto_Closing/auto_closing.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../imports.resource 3 | 4 | Suite Setup Setup Suite 5 | 6 | *** Test Cases *** 7 | Resource Leaker 8 | New Context 9 | New Page ${WELCOME_URL} 10 | 11 | New Context Is Closed After Test 12 | Get Title == Error Page 13 | 14 | Page Leaker 15 | Go To ${WELCOME_URL} 16 | New Page ${FORM_URL} 17 | Get Title == prefilled_email_form.html 18 | 19 | New Page In Same Context Is Closed After Test 20 | Get Title == Welcome Page 21 | 22 | Unhandled Alert Does Not Block Execution 23 | [Tags] debug 24 | New Page ${ERROR_URL} 25 | Click text="Do not click!" 26 | 27 | *** Keywords *** 28 | Setup Suite 29 | New Page ${ERROR_URL} 30 | Set Browser Timeout 3s Suite 31 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/Auto_Closing/auto_closing_suite_level.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Browser auto_closing_level=SUITE run_on_failure=None 3 | Resource ../imports.resource 4 | 5 | Suite Setup New Page ${ERROR_URL} 6 | 7 | Force Tags no-iframe 8 | 9 | *** Test Cases *** 10 | Resource Leaker 11 | New Context 12 | New Page ${WELCOME_URL} 13 | 14 | New Context Is Not Closed After Test 15 | Get Title == Welcome Page 16 | 17 | Page Leaker 18 | Go To ${WELCOME_URL} 19 | New Page ${FORM_URL} 20 | Get Title == prefilled_email_form.html 21 | 22 | New Page Is Not Closed After Test 23 | Get Title == prefilled_email_form.html 24 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/chromiun_channel.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Teardown Close Browser ALL 5 | 6 | *** Test Cases *** 7 | Wrong Browser With Channel 8 | Run Keyword And Expect Error 9 | ... ValueError: Must use chromium browser with channel definition 10 | ... New Browser firefox channel=chrome 11 | 12 | Use Chrome Stable With Channel Argument 13 | [Tags] not-implemented # This fails very often in CI, disable it. 14 | [Timeout] 60s # Is slow in Windows OS. 15 | New Browser chromium headless=False channel=chrome 16 | New Context 17 | ${TIMEOUT} = Set Browser Timeout 30 s 18 | Set Suite Variable ${TIMEOUT} 19 | New Page ${LOGIN_URL} 20 | [Teardown] Set Browser Timeout ${TIMEOUT} 21 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/client_certificates.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | Library ../../library/certificate.py 4 | 5 | Suite Setup Setup 6 | Suite Teardown Suite Teardown 7 | Test Teardown Close Browser ALL 8 | 9 | *** Test Cases *** 10 | Open Browser With Client Certificate 11 | New Browser browser=${BROWSER} headless=${HEADLESS} 12 | ${cert} = Create Dictionary 13 | ... origin=https://localhost:${https_port} 14 | ... certPath=${OUTPUT_DIR}/client.crt 15 | ... keyPath=${OUTPUT_DIR}/client.key 16 | ${certs} = Create List ${cert} 17 | New Context 18 | ... ignoreHTTPSErrors=True 19 | ... clientCertificates=${certs} 20 | New Page https://localhost:${https_port}/ 21 | 22 | Open Browser Without Client Certificate 23 | [Tags] no-windows-support 24 | New Browser browser=${BROWSER} headless=${HEADLESS} 25 | New Context ignoreHTTPSErrors=True 26 | ${error} = Run Keyword And Expect Error 27 | ... * New Page https://localhost:${https_port}/ 28 | Should Match Regexp 29 | ... ${error} 30 | ... Error: page\.goto: net::(?:ERR_BAD_SSL_CLIENT_AUTH_CERT|ERR_SOCKET_NOT_CONNECTED).+ 31 | 32 | *** Keywords *** 33 | Setup 34 | Generate Ca Certificate ${OUTPUT_DIR}/ca.crt ${OUTPUT_DIR}/ca.key 35 | Generate Server Certificate 36 | ... "localhost" 37 | ... ${OUTPUT_DIR}/server.crt 38 | ... ${OUTPUT_DIR}/server.key 39 | ... ${OUTPUT_DIR}/ca.crt 40 | ... ${OUTPUT_DIR}/ca.key 41 | Generate Client Certificate 42 | ... "client" 43 | ... ${OUTPUT_DIR}/client.crt 44 | ... ${OUTPUT_DIR}/client.key 45 | ... ${OUTPUT_DIR}/ca.crt 46 | ... ${OUTPUT_DIR}/ca.key 47 | ${port} = Start Test Https Server 48 | ... ${OUTPUT_DIR}/server.crt 49 | ... ${OUTPUT_DIR}/server.key 50 | ... ${OUTPUT_DIR}/ca.crt 51 | ... True 52 | Set Global Variable ${https_port} ${port} 53 | 54 | Suite Teardown 55 | Stop Test Server ${https_port} 56 | Suite Cleanup 57 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/coverageConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | logging: 'info', 3 | name: 'Browser library Coverage Report', 4 | reports: [['raw'], ['v8']] 5 | } -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/coverageConfigCombine.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | logging: 'info', 3 | name: 'Browser library Combined Coverage Report', 4 | reports: [['raw'], ['v8']] 5 | } -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/coverageConfigMD.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | logging: 'info', 3 | name: 'Browser library Coverage Report MD', 4 | reports: [['raw'], ['markdown-summary']] 5 | } -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/device_descriptors.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | *** Test Cases *** 5 | Get Devices 6 | # Has too much content for a sane assertion here 7 | ${devices} = Get Devices 8 | Should Be True ${devices.__len__() >= 6} 9 | 10 | Get Device 11 | ${device} = Get Device Galaxy S5 12 | FOR ${key} IN userAgent viewport deviceScaleFactor isMobile hasTouch defaultBrowserType 13 | Dictionary Should Contain Key ${device} ${key} 14 | END 15 | Should Be True ${device.__len__() >= 6} 16 | Should Be True ${device}[isMobile] 17 | Should Be True ${device}[hasTouch] 18 | 19 | Get Device With Screen 20 | ${device} = Get Device iPhone 11 21 | New Browser headless=${HEADLESS} 22 | New Context &{device} acceptDownloads=True 23 | 24 | Get Invalid Device Errors 25 | Run Keyword And Expect Error 26 | ... Error: No device named NonExistentDeviceName 27 | ... Get Device NonExistentDeviceName 28 | 29 | Descriptor Properly Sets Context Settings 30 | ${device} = Get Device Galaxy S5 31 | New Context &{device} 32 | New Page 33 | Get Viewport Size ALL == { "width": 360 , "height": 640 } 34 | Verify Browser Type chromium 35 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/goto.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup Close Page ALL 5 | 6 | *** Test Cases *** 7 | No Open Browser Throws 8 | Run KeyWord And Expect Error 9 | ... Error: No page open. 10 | ... GoTo "about:blank" 11 | 12 | Open GoTo GoBack GoForward 13 | [Tags] slow 14 | [Setup] New Page ${LOGIN_URL} 15 | ${status} = Go To ${WELCOME_URL} 16 | Should Be Equal ${status} ${200} 17 | Get Url == ${WELCOME_URL} 18 | Go To ${ERROR_URL} 19 | Go Back 20 | Get Url == ${WELCOME_URL} 21 | Go Back 22 | Get Url == ${LOGIN_URL} 23 | Go Forward 24 | Get Url == ${WELCOME_URL} 25 | Go Forward 26 | Get Url == ${ERROR_URL} 27 | [Teardown] Close Context 28 | 29 | Go To 404 URL 30 | [Tags] slow 31 | New Page ${LOGIN_URL} 32 | ${status} = Go To ${WELCOME_URL}_404 33 | Should Be Equal ${status} ${404} 34 | [Teardown] Close Context 35 | 36 | Timeouting Go To 37 | New Page ${LOGIN_URL} 38 | ${timeout} = Set Browser Timeout 2ms 39 | TRY 40 | Go To ${WELCOME_URL} 41 | EXCEPT TimeoutError: page.goto: Timeout 2ms exceeded* type=GLOB AS ${error} 42 | Log ${error} 43 | END 44 | [Teardown] Teardown For Timeouting Go To ${timeout} 45 | 46 | Timeouting Go To With Custom Timeout 47 | [Tags] slow 48 | New Page ${LOGIN_URL} 49 | TRY 50 | Go To ${WELCOME_URL} 3 ms 51 | EXCEPT TimeoutError: page.goto: Timeout 3ms exceeded* type=GLOB AS ${error} 52 | Log ${error} 53 | END 54 | [Teardown] Close Context 55 | 56 | *** Keywords *** 57 | Teardown For Timeouting Go To 58 | [Arguments] ${timeout} 59 | Set Browser Timeout ${timeout} 60 | Close Browser 61 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/grpc_overflow.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Timeout 1000s 5 | 6 | *** Test Cases *** 7 | GRPC Message Overflow Should Not Happen Event The Message Exceeds Default Size 8 | [Tags] slow 9 | ${aabbcc} = Set Variable AABBCC 10 | New Context 11 | New Page ${ROOT_URL}enabled_disabled_fields_form.html 12 | ${timeout} = Set Browser Timeout 2s 13 | ${msg} = Run Keyword And Expect Error 14 | ... * 15 | ... Get Attribute //${aabbcc * 1500} foo equal tidii 16 | Should Contain ${msg} Error: locator.elementHandle: Timeout 2000ms exceeded. 17 | Should Contain ${msg} waiting for locator('//${aabbcc * 80} 18 | [Teardown] Set Browser Timeout ${timeout} 19 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/hangs_setup.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Teardown Close Browser 5 | 6 | *** Variables *** 7 | ${ErrorMessage} = page.goto: Timeout 1ms exceeded. 8 | 9 | *** Test Cases *** 10 | Test GoTo With Short Default Timeout 11 | [Tags] slow 12 | New Page 13 | Set Browser Timeout 1ms 14 | Run Keyword And Expect Error *${ErrorMessage}* Go To ${LOGIN_URL} 15 | Wait For Elements State //h1 visible timeout=2 s 16 | 17 | Test New Page With Short Default Timeout 18 | New Context 19 | Set Browser Timeout 1ms 20 | Run Keyword And Expect Error *${ErrorMessage}* New Page ${LOGIN_URL} 21 | 22 | *** Keywords *** 23 | Setup 24 | ${original} = Register Keyword To Run On Failure ${None} 25 | Set Suite Variable $original 26 | 27 | Teardown 28 | Register Keyword To Run On Failure ${original} 29 | Close Browser 30 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../keywords.resource 3 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/keyword_banner.py: -------------------------------------------------------------------------------- 1 | from robot.libraries.BuiltIn import BuiltIn 2 | from Browser import Browser 3 | from assertionengine.assertion_engine import verify_assertion, AssertionOperator 4 | from typing import Optional 5 | 6 | 7 | def get_computed_banner_style(): 8 | b: Browser = BuiltIn().get_library_instance("Browser") 9 | return b.evaluate_javascript( 10 | "!prefix body", "element => window.getComputedStyle(element,':before')" 11 | ) 12 | 13 | 14 | def get_real_page_source( 15 | operator: AssertionOperator = None, expected: Optional[str] = None 16 | ): 17 | b: Browser = BuiltIn().get_library_instance("Browser") 18 | return verify_assertion(b.get_page_source(), operator, expected) 19 | 20 | 21 | def get_banner_content( 22 | operator: AssertionOperator = None, expected: Optional[str] = None 23 | ): 24 | style = get_computed_banner_style() 25 | content = ( 26 | BuiltIn().evaluate(style["content"]) 27 | if style["content"].startswith('"') 28 | else style["content"] 29 | ) 30 | return verify_assertion(content, operator, expected) 31 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/local_storage.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Page ${LOGIN_URL} 5 | Suite Teardown Close Browser ALL 6 | 7 | *** Test Cases *** 8 | Set And Get Local Storage 9 | Local Storage Set Item Tidii Kala 10 | ${item} = Local Storage Get Item Tidii 11 | Should Be Equal ${item} Kala 12 | 13 | Local Storage Get Item Should Fail If Item Does Not Exist 14 | ${item} = Local Storage Get Item Kala 15 | Should Be Equal ${item} ${None} 16 | 17 | Local Storage Get Item Default Error 18 | [Tags] slow 19 | Run Keyword And Expect Error 20 | ... localStorage 'None' (nonetype) should be 'Tidii' (str) 21 | ... Local Storage Get Item Kala == Tidii 22 | 23 | Local Storage Get Item Custom Error 24 | [Tags] slow 25 | Run Keyword And Expect Error 26 | ... My error 27 | ... Local Storage Get Item Kala == Tidii My error 28 | 29 | Remove Local Storage Item 30 | Local Storage Set Item Foo bar 31 | ${item} = Local Storage Get Item Foo 32 | Should Be Equal ${item} bar 33 | LocalStorage Remove Item Foo 34 | ${item} = Local Storage Get Item Foo 35 | Should Be Equal ${item} ${None} 36 | 37 | Clear Local Storage 38 | Local Storage Set Item Foo bar 39 | ${item} = Local Storage Get Item Foo 40 | Should Be Equal ${item} bar 41 | LocalStorage Clear 42 | ${item} = Local Storage Get Item Foo 43 | Should Be Equal ${item} ${None} 44 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/new_page_should_not_timeout.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup Setup 5 | Suite Teardown Teardown 6 | 7 | *** Test Cases *** 8 | New Page Will Not Timeout 9 | [Tags] slow 10 | New Page ${SLOW_PAGE} 11 | Get Title == Slow page 12 | 13 | New Page Will Timeout And Page Will Be Removed From Catalog 14 | [Tags] slow 15 | Set Browser Timeout 1s 16 | New Context 17 | ${Catalog} = Get Browser Catalog 18 | TRY 19 | New Page ${SLOW_PAGE} 20 | EXCEPT *Timeout* type=glob 21 | ${new_catalog} = Get Browser Catalog 22 | Should Be Equal ${Catalog} ${new_catalog} 23 | ELSE 24 | Fail Expected timeout 25 | END 26 | 27 | *** Keywords *** 28 | Setup 29 | Set Browser Timeout 15s scope=Suite 30 | ${original} = Register Keyword To Run On Failure ${None} 31 | Set Suite Variable $original 32 | New Browser headless=${HEADLESS} 33 | 34 | Teardown 35 | Register Keyword To Run On Failure ${original} 36 | Close Browser 37 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/no_httpCredentials_logging.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Browser 5 | Test Teardown Close Context 6 | 7 | *** Test Cases *** 8 | New Context No Mask For HttpCredentials When Not Defined 9 | [Documentation] ... 10 | ... LOG 1:2 INFO REGEXP: ^((?!httpCredentials).)*$ 11 | ... LOG 1:2 INFO REGEXP: .*ignoreHTTPSErrors.* 12 | ... LOG 1:4 INFO REGEXP: ^((?!httpCredentials).)*$ 13 | ... LOG 1:4 INFO REGEXP: .*ignoreHTTPSErrors.* 14 | New Context 15 | 16 | New Context Mask For HttpCredentials When Defined 17 | TRY 18 | New Context httpCredentials={'username': 'name', 'password': 'pwd'} 19 | EXCEPT ValueError: Direct assignment of values or variables as 'httpCredentials' is not allowed. Use special variable syntax ($var instead of \${var}) to prevent variable values from being spoiled. 20 | Log Correct Error Message 21 | END 22 | 23 | New Context HttpCredentials Resolved 24 | [Documentation] ... 25 | ... LOG 3:2 INFO REGEXP: .*"httpCredentials": "XXX".* 26 | ... LOG 3:2 INFO REGEXP: .*ignoreHTTPSErrors.* 27 | ... LOG 3:4 INFO REGEXP: .*httpCredentials(\"|'):\\s(\"|')XXX(\"|').* 28 | ... LOG 3:4 INFO REGEXP: .*ignoreHTTPSErrors.* 29 | ${pwd} = Set Variable pwd 30 | ${username} = Set Variable name 31 | New Context httpCredentials={'username': '$username', 'password': '$pwd'} 32 | 33 | New Context HttpCredentials Resolved As Dict 34 | [Documentation] ... 35 | ... LOG 4:2 INFO REGEXP: .*"httpCredentials": "XXX".* 36 | ... LOG 4:2 INFO REGEXP: .*ignoreHTTPSErrors.* 37 | ... LOG 4:4 INFO REGEXP: .*httpCredentials(\"|'):\\s(\"|')XXX(\"|').* 38 | ... LOG 4:4 INFO REGEXP: .*ignoreHTTPSErrors.* 39 | ${pwd} = Set Variable pwd 40 | ${username} = Set Variable name 41 | ${credentials} = Create Dictionary username=$username password=$pwd 42 | New Context httpCredentials=${credentials} 43 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/offline.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | *** Test Cases *** 5 | Toggling Offline Disables Connection 6 | [Tags] slow 7 | New Page ${LOGIN_URL} 8 | Set Offline 9 | # The element checking for network pings every 500ms 10 | Sleep 600ms 11 | Get Text \#network_pinger == no connection 12 | 13 | Creating Offline Context Works 14 | New Context offline=True 15 | Run Keyword And Expect Error 16 | ... STARTS:Error: page.goto: net::ERR_INTERNET_DISCONNECTED 17 | ... New Page ${LOGIN_URL} 18 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/open_browser.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Teardown Close Browser ALL 5 | 6 | *** Test Cases *** 7 | Open Browser With Timeout Of Zero Seconds 8 | New Browser browser=${BROWSER} headless=${HEADLESS} timeout=0 seconds 9 | New Browser browser=${BROWSER} headless=${HEADLESS} timeout=0 second 10 | New Browser browser=${BROWSER} headless=${HEADLESS} timeout=0 s 11 | New Browser browser=${BROWSER} headless=${HEADLESS} timeout=0s 12 | 13 | Open Browser With Default Timeout 14 | New Browser browser=${BROWSER} headless=${HEADLESS} 15 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/record_har.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Browser headless=${HEADLESS} 5 | 6 | *** Test Cases *** 7 | Har Path Only Defined 8 | ${har} = Create Dictionary path=${OUTPUT_DIR}/har-1.file 9 | New Context recordHar=${har} 10 | New Page ${LOGIN_URL} 11 | Close Context 12 | File Should Not Be Empty ${OUTPUT_DIR}/har-1.file 13 | 14 | Har Path And OmitContent Defined 15 | ${har} = Create Dictionary path=${OUTPUT_DIR}/har-2.file omitContent=True 16 | New Context recordHar=${har} 17 | New Page ${LOGIN_URL} 18 | Close Context 19 | File Should Not Be Empty ${OUTPUT_DIR}/har-2.file 20 | 21 | Har Path And OmitContent Defined As String 22 | New Context recordHar={"path": r"${OUTPUT_DIR}/har-3.file", "omitContent": "True"} 23 | New Page ${LOGIN_URL} 24 | Close Context 25 | File Should Not Be Empty ${OUTPUT_DIR}/har-3.file 26 | 27 | No Har Created 28 | Remove File path 29 | New Context 30 | New Page ${LOGIN_URL} 31 | Close Context 32 | ${files} = List Files In Directory ${OUTPUT_DIR} har* 33 | Length Should Be ${files} 3 34 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/reload.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Context 5 | Suite Teardown Close Context 6 | 7 | *** Test Cases *** 8 | Reload 9 | New Page ${WELCOME_URL} 10 | Reload 11 | Reload timeout=10s waitUntil=load 12 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/storage_state.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Browser headless=${HEADLESS} 5 | 6 | *** Test Cases *** 7 | Save Storage State 8 | New Context 9 | New Page ${LOGIN_URL} 10 | Add Cookies For Storage 11 | ${STATE_FILE} = Save Storage State 12 | Set Suite Variable ${STATE_FILE} 13 | File Should Not Be Empty ${state_file} 14 | 15 | Restore Storage State 16 | New Context storageState=${STATE_FILE} 17 | ${cookie} = Get Cookie Foo 18 | Should Be Equal ${cookie.value} Bar 19 | ${cookie} = Get Cookie Key 20 | Should Be Equal ${cookie.value} Value 21 | 22 | Restore Storage State With Invalid Path 23 | Run Keyword And Expect Error 24 | ... ValueError: storageState argument value '/not/here' is not file, but it should be. 25 | ... New Context storageState=/not/here 26 | 27 | Restore Storage State With Invalid File 28 | Append To File ${OUTPUT_DIR}/invalid_state_file.json not valid json 29 | Run Keyword And Expect Error 30 | ... SyntaxError*JSON* 31 | ... New Context storageState=${OUTPUT_DIR}/invalid_state_file.json 32 | 33 | *** Keywords *** 34 | Add Cookies For Storage 35 | ${url} = Get Url 36 | Add Cookie Foo Bar url=${url} 37 | Add Cookie Key Value url=${url} 38 | Evaluate JavaScript ${None} localStorage.setItem('bgcolor', 'red'); 39 | -------------------------------------------------------------------------------- /atest/test/01_Browser_Management/tracing_groups.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import Union 4 | 5 | 6 | def get_trace_lines(path: Path) -> list[dict]: 7 | with path.open(encoding="utf-8") as file: 8 | return [ 9 | json.loads(line) for line in file.readlines() if '"type":"before"' in line 10 | ] 11 | 12 | 13 | def get_file_line(file: Union[Path, None], line: int, column: int) -> str: 14 | if file is None: 15 | return 16 | with file.open(encoding="utf-8") as file: 17 | return file.readlines()[line - 1] 18 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup Open Browser To Form Page 5 | Suite Teardown Close Browser 6 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/click.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Browser ${BROWSER} headless=${HEADLESS} 5 | Test Setup Ensure Open Page ${LOGIN_URL} 6 | 7 | *** Test Cases *** 8 | Click Button 9 | Click css=input#login_button 10 | Get Text text=Login failed. Invalid user name and/or password. 11 | 12 | Click Nonmatching Selector 13 | [Tags] no-iframe 14 | ${originaltimeout} = Set Browser Timeout 50ms 15 | Run Keyword And Expect Error 16 | ... *Error: locator.click: Timeout 50ms exceeded.*waiting for locator('notamatch')* 17 | ... Click css=notamatch 18 | [Teardown] Set Browser Timeout ${originaltimeout} 19 | 20 | Click With Invalid Selector 21 | Run Keyword And Expect Error 22 | ... *input?type="submit"?X' is not a valid selector.* 23 | ... Click 24 | ... input[type="submit"]X 25 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/cookies_no_Browser.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup Close Browser ALL 5 | 6 | *** Test Cases *** 7 | Cookies From Closed Context 8 | Run Keyword And Expect Error 9 | ... Error: no open context. 10 | ... Get Cookies 11 | 12 | Add Cookie Should Fail If Context Is Not Open 13 | Run Keyword And Expect Error 14 | ... Error: no open context. 15 | ... Add Cookie Foo Bar url=${ELEMENT_STATE_URL} 16 | 17 | Delete All Cookies From Closed Context 18 | Run Keyword And Expect Error 19 | ... Error: no open context. 20 | ... Delete All Cookies 21 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/focus.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup New Page ${LOGIN_URL} 5 | 6 | *** Test Cases *** 7 | Add Style 8 | [Tags] no-iframe 9 | Add Style Tag \#username_field:focus {background-color: aqua;} 10 | Focus \#username_field 11 | Get Style \#username_field background-color == rgb(0, 255, 255) 12 | 13 | Focus With Strict 14 | Run Keyword And Expect Error 15 | ... *strict mode violation*//input*resolved to ${INPUT_ELEMENT_COUNT_IN_LOGIN} elements* 16 | ... Focus //input 17 | Set Strict Mode False 18 | Focus //input 19 | [Teardown] Set Strict Mode True 20 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../keywords.resource 3 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/invalid_assertions.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup New Page ${LOGIN_URL} 5 | 6 | *** Test Cases *** 7 | Invalids Will Raise Error Directly From Robot Framework 8 | Run Keyword And Expect Error 9 | ... *'assertion_operator' got value 'invalidOperator' that cannot be converted to AssertionOperator* 10 | ... Get Title invalidOperator value 11 | Run Keyword And Expect Error 12 | ... *'assertion_operator' got value 'faultyOps' that cannot be converted to AssertionOperator* Get URL 13 | ... faultyOps 14 | Run Keyword And Expect Error 15 | ... *'assertion_operator' got value '!=!' that cannot be converted to AssertionOperator* Get Text h1 16 | ... !=! blaah 17 | Run Keyword And Expect Error 18 | ... *'assertion_operator' got value 'equas' that cannot be converted to AssertionOperator* 19 | ... Get Property h1 innerText equas plaah 20 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/invalid_test_upload_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarketSquare/robotframework-browser/e98197faab8e0f18491ab8eed938a472dc8e8bbe/atest/test/02_Content_Keywords/invalid_test_upload_file -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/keyboardInput.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup New Page ${LOGIN_URL} 5 | 6 | *** Test Cases *** 7 | Keyboard Inputtype Type 8 | Focus \#username_field 9 | Keyboard Input type 0123456789 10 | Get Text \#username_field == 0123456789 11 | Get Text \#countKeyPress == 10 12 | 13 | Keyboard Inputtype InsertText 14 | Focus \#username_field 15 | Keyboard Input insertText 0123456789 16 | Get Text \#username_field == 0123456789 17 | Get Text \#countKeyPress == 1 18 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/keypress.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup New Page ${FORM_URL} 5 | 6 | *** Test Cases *** 7 | Press Keys Generate Characters 8 | Clear Text input[name="name"] 9 | Press Keys input[name="name"] H e l l o Space W o r l d ! 10 | Get Text input[name="name"] == Hello World! 11 | 12 | Press Keys With Strict 13 | Run Keyword And Expect Error 14 | ... *strict mode violation*//input*resolved to 12 elements* 15 | ... Press Keys //input Foo 16 | Set Strict Mode False 17 | Press Keys //input T i d i i 18 | [Teardown] Set Strict Mode True 19 | 20 | Press Key Combinations Of Keystrokes In TextField 21 | Press Keys input[name="email"] Home Shift+End Delete 22 | Press Keys input[name="email"] Shift+KeyA KeyA 23 | Get Text input[name="email"] == Aa 24 | 25 | Press Keys Combination Of Keystrokes In Select List 26 | Click select[name="possible_channels"] > option[value="email"] 27 | Browser.Press Keys select[name="possible_channels"] Shift+ArrowDown 28 | Browser.Press Keys select[name="possible_channels"] Shift+ArrowDown 29 | Get Selected Options select[name="possible_channels"] value == email phone directmail 30 | 31 | Press Keys With Nonmatching Selector 32 | [Tags] no-iframe 33 | Set Browser Timeout 50ms 34 | Run Keyword And Expect Error 35 | ... *Error: locator.press: Timeout 50ms exceeded.*waiting for locator('notamatch')* 36 | ... Press Keys css=notamatch F 37 | [Teardown] Set Browser Timeout ${PLAYWRIGHT_TIMEOUT} 38 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/keys/private_key.json: -------------------------------------------------------------------------------- 1 | {"private_key": "KplzCbZghvC7nLK4RQFAs0gZF4L+VduoqXz1lcs9h177qdYlnu+CILGllrKkITPmZL8A3aeckdy3WD1SX9T8zQeSNE1u9F1O", "salt": "C2vgJgTCVhfdsaOMV45pHw==", "ops": 3, "mem": 268435456} -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/keys/public_key.key: -------------------------------------------------------------------------------- 1 | ohDburpv+H/Wa06UD5QM3jtQH8rZUAUaoTUWbghsZ2c= -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/readme_example.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Teardown Close Page 5 | 6 | Force Tags no-iframe need-inet 7 | 8 | *** Test Cases *** 9 | Example 10 | ${old_timeout} = Set Browser Timeout 60 seconds 11 | New Page https://playwright.dev 12 | Get Text h1 contains Playwright 13 | Set Browser Timeout ${old_timeout} 14 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/solve_draggame.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup New Page ${DRAGGAME_URL} 5 | 6 | *** Test Cases *** 7 | Move Obstacle To Goal And Make A Goal 8 | Get Text h2 == Put the circle in the goal 9 | Drag And Drop id=blue-box id=invisible-element steps=10 10 | Drag And Drop id=red-circle id=goal-post steps=10 11 | Get Text h2 == GOAL!!! 12 | 13 | Move Obstacle Away And Drag And Drop 14 | Get Text h2 == Put the circle in the goal 15 | Hover id=blue-box 16 | Mouse Button down 17 | Mouse Move Relative To id=blue-box 200 18 | Mouse Button up 19 | Drag And Drop id=red-circle id=goal-post steps=10 20 | Get Text h2 == GOAL!!! 21 | 22 | Test 23 | [Tags] slow 24 | [Setup] New Page 25 | FOR ${i} IN RANGE 20 26 | Go To ${SHELLGAME_URL} 27 | Mouse Move Relative To id=indicator -100 28 | Mouse Button click 29 | Get Text h1 == CORRECT :D 30 | END 31 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/styles.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup New Page ${LOGIN_URL} 5 | 6 | Force Tags no-iframe 7 | 8 | *** Test Cases *** 9 | Add Style 10 | Add Style Tag \#goes_hidden{color:aqua} 11 | 12 | Verify Style 13 | Add Style Tag \#goes_hidden{color:aqua} 14 | Get Style \#goes_hidden color == rgb(0, 255, 255) 15 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/tab.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | *** Test Cases *** 5 | Tab Button Without hasTouch 6 | [Setup] Open Page With Touch Not Enabled 7 | TRY 8 | Tap css=input#login_button 9 | EXCEPT *locator.tap*hasTouch context option to enable touch support* type=GLOB AS ${error} 10 | Log Corrent error: ${error} 11 | END 12 | 13 | Tab Button 14 | [Setup] Open Page With Touch Enabled 15 | Tap css=input#login_button 16 | Get Text text=Login failed. Invalid user name and/or password. 17 | 18 | Tab Button With Trial 19 | [Setup] Open Page With Touch Enabled 20 | Tap css=input#login_button trial=True 21 | Get Text text=Please input your user name and password and click the login button. 22 | 23 | Tab Button With Options 24 | [Setup] Open Page With Touch Enabled 25 | Tap css=input#login_button force=True noWaitAfter=True position_x=3 position_y=4 26 | Get Text text=Login failed. Invalid user name and/or password. 27 | 28 | Tab Button With Modifiers 29 | [Setup] Open Page With Touch Enabled 30 | Tap \#clickWithOptions Shift Alt 31 | Get Text text=Please input your user name and password and click the login button. 32 | 33 | *** Keywords *** 34 | Open Page With Touch Enabled 35 | New Context hasTouch=${True} 36 | New Page ${LOGIN_URL} 37 | 38 | Open Page With Touch Not Enabled 39 | New Browser ${BROWSER} headless=${HEADLESS} 40 | New Context hasTouch=${False} 41 | New Page ${LOGIN_URL} 42 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/test_record_selector.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Browser headless=False 5 | Suite Teardown Close Browser 6 | 7 | *** Test Cases *** 8 | Finds A Selector 9 | [Tags] no-mac-support slow 10 | [Timeout] 2 minutes 11 | New Page ${LOGIN_URL} 12 | ${recording} = Promise To record selector 13 | Hover \#browser-library-selector-recorder >> h5 14 | Mouse Button down 15 | Mouse Move Relative To \#browser-library-selector-recorder >> h5 300 300 16 | Mouse Button up 17 | Hover h1 18 | Click id=browser-library-select-selector 19 | Click id=browser-library-selection-ok-button 20 | ${selector} = Wait For ${recording} 21 | Get Text ${selector} == Login Page 22 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/test_upload_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarketSquare/robotframework-browser/e98197faab8e0f18491ab8eed938a472dc8e8bbe/atest/test/02_Content_Keywords/test_upload_file -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/title_should_be.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Page ${FORM_URL} 5 | 6 | *** Test Cases *** 7 | Test Server Title 8 | New Page ${LOGIN_URL}/ 9 | Get Title == Login Page 10 | 11 | About:blank Title 12 | New Page about:blank 13 | Get Title == ${EMPTY} 14 | 15 | Get Title Default Error 16 | Set Retry Assertions For 100ms 17 | Run Keyword And Expect Error 18 | ... Title 'prefilled_email_form.html' (str) should be 'Not Here' (str) 19 | ... Get Title == Not Here 20 | [Teardown] Set Retry Assertions For 1s 21 | 22 | Get Title Custom Error 23 | Set Retry Assertions For 100ms 24 | Run Keyword And Expect Error 25 | ... Tidii 26 | ... Get Title == Not Here Tidii 27 | [Teardown] Set Retry Assertions For 1s 28 | -------------------------------------------------------------------------------- /atest/test/02_Content_Keywords/virtual_keyboard.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup New Page ${FORM_URL} 5 | 6 | *** Test Cases *** 7 | Keyboard Key Inputs Characters 8 | Clear Text input[name="name"] 9 | Click input[name="name"] 10 | Keyboard Key press H 11 | Keyboard Key press e 12 | Keyboard Key press l 13 | Keyboard Key press l 14 | Keyboard Key press o 15 | Get Text input[name="name"] == Hello 16 | 17 | Select List Options 18 | Click select[name="possible_channels"] > option[value="email"] 19 | Keyboard Key down Shift 20 | Keyboard Key press ArrowDown 21 | Keyboard Key press ArrowDown 22 | Get Selected Options select[name="possible_channels"] value == email phone directmail 23 | -------------------------------------------------------------------------------- /atest/test/03_Waiting/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup Open Browser To No Page 5 | Suite Teardown Close Browser ALL 6 | -------------------------------------------------------------------------------- /atest/test/03_Waiting/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../keywords.resource 3 | -------------------------------------------------------------------------------- /atest/test/03_Waiting/promise_to.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup New Page ${WAIT_URL} 5 | 6 | *** Test Cases *** 7 | Promise To With Type Conversion 8 | ${promise} = Promise To Click id=victim button=left 9 | Select Options By id=dropdown value True enabled 10 | Click With Options id=submit noWaitAfter=True 11 | Wait For ${promise} 12 | 13 | Promise To Convert Type 14 | ${promise} = Promise To Click With Options id=clickWithOptions left clickCount=4 delay=200 ms 15 | Go To ${LOGIN_URL} 16 | Wait For ${promise} 17 | Get Text id=click_count == 4 18 | Get Text id=mouse_delay_time validate int(value) > 100 and int(value) < 300 19 | Get Text id=mouse_button == left 20 | 21 | Could Not Find Keyword W Promise To 22 | [Setup] NONE 23 | TRY 24 | Promise To Could Not Find Keyword 25 | EXCEPT ValueError: Unknown keyword 'Could Not Find Keyword'! 'Promise To' can only be used with Browser keywords. AS ${e} 26 | Log ${e} 27 | ELSE 28 | FAIL Should have failed 29 | END 30 | 31 | Promise To With *args 32 | ${promise} = Promise To 33 | ... Click With Options 34 | ... id=clickWithOptions 35 | ... left 36 | ... SHIFT 37 | ... ALT 38 | ... clickCount=4 39 | ... delay=200 ms 40 | Go To ${LOGIN_URL} 41 | Wait For ${promise} 42 | Get Text id=click_count == 4 43 | Get Text id=mouse_delay_time validate int(value) > 100 and int(value) < 300 44 | Get Text id=mouse_button == left 45 | Get Text id=shift_key == true 46 | Get Text id=alt_key == true 47 | -------------------------------------------------------------------------------- /atest/test/03_Waiting/wait_for_page_load_state.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Test Setup Ensure Location ${LOGIN_URL} 5 | 6 | *** Test Cases *** 7 | Wait For Page Load States Load 8 | Go To ${ROOT_URL}delayed-load.html 9 | Wait For Load State state=load 10 | 11 | Wait For Page Load States Commit 12 | Wait For Load State state=commit 13 | 14 | Wait For Page Load States Networkidle 15 | Go To ${ROOT_URL}delayed-load.html 16 | Wait For Load State state=networkidle 17 | Go To ${ROOT_URL}delayed-load.html 18 | Get Text \#server_delayed_response == Server response after 400ms 19 | TRY 20 | Wait For Load State state=networkidle timeout=0.02s 21 | EXCEPT AS ${error} 22 | Should Start With ${error} TimeoutError: page.waitForLoadState: 23 | END 24 | 25 | Wait For Page Load States Domcontentloaded 26 | Go To ${ROOT_URL}delayed-load.html 27 | Get Text \#server_delayed_response == Server response after 400ms 28 | Wait For Load State state=domcontentloaded timeout=1s 29 | -------------------------------------------------------------------------------- /atest/test/04_frames/deep_frames.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Browser ${BROWSER} headless=${HEADLESS} 5 | Suite Teardown Close Browser 6 | Test Setup Ensure Location ${DEEP_FRAMES_URL} 7 | 8 | *** Test Cases *** 9 | First Level 10 | Get Text h1 == A HTML 11 | 12 | Second Level 13 | Get Text id=b >>> id=bb == B HTML 14 | 15 | Third Level 16 | ${style} = Get Style id=b >>> id=c >>> id=cc width 17 | Should Be Equal ${style} auto 18 | Get Text id=b >>> id=c >>> id=cc == This is c 19 | 20 | Third Level Executing JS 21 | Evaluate JavaScript id=b >>> id=c >>> id=cc (element) => element.textContent = "foo" 22 | Get Text id=b >>> id=c >>> id=cc == foo 23 | 24 | Third Level From Second 25 | New Page ${DEEP_FRAMES_2ND_URL} 26 | Get Text id=c >>> id=cc == This is c 27 | 28 | Pierce IFrame Implicit CSS, Xpath And Text 29 | [Setup] Ensure Location ${FRAMES_URL} 30 | Click iframe[name="left"] >>> "foo" 31 | Get Property \#left >>> a[href="foo.html"] target == right 32 | Get Text //iframe[@id="right"] >>> p == You're looking at foo. 33 | 34 | Pierce IFrame Nested Selectors 35 | [Setup] Ensure Location ${FRAMES_URL} 36 | Click body >> [src="left.html"] >>> body >> //input[@name="searchbutton"] 37 | Get Text xpath=//body >> css=#right >>> xpath=/html >> css=body > p == You're looking at search results. 38 | -------------------------------------------------------------------------------- /atest/test/04_frames/frames_and_elements.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Page ${FRAMES_BUG_REPORT} 5 | 6 | *** Test Cases *** 7 | Get Text With Element 8 | ${count} = Get Elements css=th 9 | Length Should Be ${count} 3 10 | Get Text ${count}[0] 11 | Get Text ${count}[1] 12 | Get Text ${count}[2] 13 | 14 | Get Text With Element From Frames 15 | ${count} = Get Elements css=iframe[id="test frame"] >>> css=th 16 | Length Should Be ${count} 3 17 | Get Text ${count}[2] 18 | Get Text ${count}[1] 19 | Get Text ${count}[0] 20 | -------------------------------------------------------------------------------- /atest/test/04_frames/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../keywords.resource 3 | -------------------------------------------------------------------------------- /atest/test/05_JS_Tests/another.js: -------------------------------------------------------------------------------- 1 | exports.__esModule = true; 2 | exports.myOtherKeyword = (arg, logger) => { 3 | logger("Logging something else"); 4 | return arg; 5 | } -------------------------------------------------------------------------------- /atest/test/05_JS_Tests/http.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Page ${LOGIN_URL} 5 | 6 | *** Variables *** 7 | &{expected get json body} = greeting=HELLO 8 | &{expected post json body} = name=John id=${1} 9 | &{expected put json body} = name=Jane id=${3} 10 | 11 | *** Test Cases *** 12 | GET With Text Response 13 | &{response} = HTTP /api/get/text 14 | Should Be Equal ${response.body} HELLO 15 | Should Be Equal ${response.status} ${200} 16 | Should Be Equal ${response.headers['content-type']} text/html; charset=utf-8 17 | 18 | GET With Json Response 19 | &{response} = HTTP /api/get/json 20 | Should Be Equal ${response.body} ${expected get json body} 21 | Should Be Equal ${response.status} ${200} 22 | Should Be Equal ${response.headers['content-type']} application/json; charset=utf-8 23 | 24 | GET With Error 25 | &{response} = HTTP /api/get/doesntexist 26 | Should Be Equal ${response.status} ${404} 27 | 28 | POST 29 | &{response} = HTTP /api/post POST {"name": "John"} 30 | Should Be Equal ${response.body} ${expected post json body} 31 | 32 | PUT 33 | &{response} = HTTP /api/put PUT {"name": "Jane"} 34 | Should Be Equal ${response.body} ${expected put json body} 35 | 36 | PATCH 37 | &{response} = HTTP /api/patch PATCH {"name": "Jane"} 38 | Should Be Equal ${response.body} ${expected put json body} 39 | 40 | DELETE 41 | &{response} = HTTP /api/delete DELETE {"name": "Jane"} 42 | Should Be Equal ${response.status} ${200} 43 | 44 | HEAD 45 | &{response} = HTTP /api/get/json 46 | Should Be Equal ${response.status} ${200} 47 | Should Be Equal ${response.headers['content-type']} application/json; charset=utf-8 48 | -------------------------------------------------------------------------------- /atest/test/05_JS_Tests/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../keywords.resource 3 | -------------------------------------------------------------------------------- /atest/test/05_JS_Tests/jsextension.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Browser jsextension=${CURDIR}/funky.js 3 | Resource imports.resource 4 | 5 | Force Tags no-iframe 6 | 7 | *** Test Cases *** 8 | Test Lazy Playwright Loading 9 | [Documentation] Tests that Playwright is loaded if jsextension is used 10 | ${browser_lib} = Get Library Instance Browser 11 | Should Be True isinstance($browser_lib._playwright, Browser.playwright.Playwright) 12 | 13 | Promise To Call Custom Js Keyword 14 | New Page 15 | ${promise} = Promise To My Funky keyword h1 16 | Go To ${LOGIN_URL} 17 | Wait For ${promise} 18 | Get Text h1 == Funk yeah! 19 | -------------------------------------------------------------------------------- /atest/test/05_JS_Tests/jsextension_comma.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Browser jsextension=${CURDIR}/funky.js,${CURDIR}/another.js 3 | Resource imports.resource 4 | 5 | Force Tags no-iframe 6 | 7 | *** Test Cases *** 8 | Calling Custom Js Keyword 9 | New Page ${LOGIN_URL} 10 | Get Text h1 == Login Page 11 | MyFunkyKeyword h1 12 | Get Text h1 == Funk yeah! 13 | 14 | List Imports 15 | ${r} = My Other Keyword test 16 | Should Be Equal ${r} test 17 | -------------------------------------------------------------------------------- /atest/test/05_JS_Tests/wrong.js: -------------------------------------------------------------------------------- 1 | function selfIsReserved(self) { 2 | return self; 3 | } 4 | 5 | exports.__esModule = true; 6 | exports.selfIsReserved = selfIsReserved; 7 | -------------------------------------------------------------------------------- /atest/test/06_Examples/crawling.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Collections 3 | Resource imports.resource 4 | 5 | Force Tags no-iframe 6 | 7 | *** Test Cases *** 8 | Normal Crawling 9 | Set Test Variable @{TITLES} @{EMPTY} 10 | ${urls} = Crawl Site ${LINKER_URL} My page keyword 11 | Log List ${TITLES} 12 | Sort List ${TITLES} 13 | ${expected} = Create List Always Link 1 Link 2 Link 3 Link 4 14 | Lists Should Be Equal ${TITLES} ${expected} 15 | 16 | Crawling Only Limited Pages 17 | Set Test Variable @{TITLES} @{EMPTY} 18 | ${urls} = Crawl Site ${LINKER_URL} My page keyword max_number_of_page_to_crawl=4 19 | Log List ${TITLES} 20 | Sort List ${TITLES} 21 | ${expected} = Create List Always Link 1 Link 2 Link 3 22 | Lists Should Be Equal ${TITLES} ${expected} 23 | 24 | Crawling Only Limited Depth 25 | Set Test Variable @{TITLES} @{EMPTY} 26 | ${urls} = Crawl Site ${LINKER_URL} My page keyword max_depth_to_crawl=1 27 | Log List ${TITLES} 28 | Sort List ${TITLES} 29 | ${expected} = Create List Always Link 1 Link 2 30 | Lists Should Be Equal ${TITLES} ${expected} 31 | 32 | *** Keywords *** 33 | My Page Keyword 34 | ${title} = Get Title 35 | Log ${TITLES} 36 | Append To List ${TITLES} ${title} 37 | -------------------------------------------------------------------------------- /atest/test/06_Examples/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../keywords.resource 3 | -------------------------------------------------------------------------------- /atest/test/06_Examples/js_evaluation.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | Suite Setup New Browser ${BROWSER} headless=${HEADLESS} 5 | Test Setup New Page ${LOGIN_URL} 6 | 7 | *** Test Cases *** 8 | Mutate Element On Page With ElementHandle 9 | ${ref} = Get Element h1 10 | Get Property ${ref} innerText == Login Page 11 | Evaluate JavaScript ${ref} (elem) => elem.innerText = "abc" 12 | Get Property ${ref} innerText == abc 13 | 14 | Wait For Progress Bar 15 | [Tags] slow 16 | ${promise} = Promise To Wait For Function element => element.style.width=="100%" 17 | ... selector=\#progress_bar timeout=4s 18 | Click \#progress_bar 19 | Wait For ${promise} 20 | -------------------------------------------------------------------------------- /atest/test/06_Examples/presenter_mode.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../variables.resource 3 | Library Browser enable_presenter_mode=True 4 | Library ../../library/presenter_mode.py 5 | 6 | Suite Setup New Browser headless=False 7 | Suite Teardown Close Browser 8 | 9 | Force Tags slow no-iframe 10 | 11 | *** Test Cases *** 12 | Filling The Text With True 13 | New Page ${LOGIN_URL} 14 | Type Text input#username_field user 15 | 16 | Filling The Text With Settings 17 | Set Presenter Mode {"color": "red", "duration": "1s", "style": "solid"} 18 | New Page ${LOGIN_URL} 19 | Type Text input#username_field user 20 | [Teardown] Set Presenter Mode False 21 | -------------------------------------------------------------------------------- /atest/test/06_Examples/promised_cat_and_dog.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | *** Test Cases *** 5 | Shows A Cat And A Dog 6 | [Tags] no-mac-support slow 7 | Set Browser Timeout 5s scope=Test 8 | Set Retry Assertions For 3s scope=Test 9 | New Page ${DOG_AND_CAT_URL} 10 | Get Text id=texts == Beginning 11 | ${cat_promise} = Promise To Get Text id=texts == Cat 12 | ${dog_promise} = Promise To Get Text id=texts == Dog 13 | Click id=clicker 14 | Wait For ${cat_promise} ${dog_promise} 15 | Get Text id=texts == The End 16 | -------------------------------------------------------------------------------- /atest/test/06_Examples/webcomponent.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | *** Test Cases *** 5 | Can Access Web Component 6 | New Page ${WEBCOMPONENT_PAGE} 7 | Get Text my-web-component == Hello hoard 8 | Get Text id=container >> id=inside == Inside Shadow DOM 9 | -------------------------------------------------------------------------------- /atest/test/07_Failing/custom_failure_screenshot.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Browser run_on_failure=Take Screenshot \ custom-fail timeout=3s 3 | Library OperatingSystem 4 | Resource imports.resource 5 | 6 | Force Tags slow 7 | 8 | *** Test Cases *** 9 | Failing With Custom Screenshot 10 | New Page ${ERROR_URL} 11 | TRY 12 | Click .nonexisting4 13 | EXCEPT TimeoutError: locator.click: Timeout 3000ms exceeded* type=GLOB AS ${error} 14 | Log ${error} 15 | END 16 | 17 | Check Screenshot 18 | File Should Exist ${OUTPUT DIR}/browser/screenshot/custom-fail.png 19 | -------------------------------------------------------------------------------- /atest/test/07_Failing/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../variables.resource 3 | Resource ../keywords.resource 4 | -------------------------------------------------------------------------------- /atest/test/07_Failing/screenshot_on_failure.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library OperatingSystem 3 | Resource imports.resource 4 | 5 | Suite Setup Check Screenshots Before 6 | 7 | Force Tags slow 8 | 9 | *** Test Cases *** 10 | Failing With Screenshot 1 11 | New Page ${ERROR_URL} 12 | TRY 13 | Click .nonexisting 14 | EXCEPT TimeoutError* type=GLOB AS ${error} 15 | Log ${error} 16 | END 17 | 18 | Failing With Screenshot 2 19 | New Page ${ERROR_URL} 20 | TRY 21 | Click .nonexisting 22 | EXCEPT TimeoutError* type=GLOB AS ${error} 23 | Log ${error} 24 | END 25 | 26 | Failing With Screenshot 3 27 | New Page ${ERROR_URL} 28 | TRY 29 | Click .nonexisting 30 | EXCEPT TimeoutError* type=GLOB AS ${error} 31 | Log ${error} 32 | END 33 | 34 | Check Screenshots 35 | File Should Exist ${OUTPUT DIR}/browser/screenshot/fail-screenshot-1.png 36 | File Should Exist ${OUTPUT DIR}/browser/screenshot/fail-screenshot-2.png 37 | File Should Exist ${OUTPUT DIR}/browser/screenshot/fail-screenshot-3.png 38 | File Should Not Exist ${OUTPUT DIR}/browser/screenshot/fail-screenshot-4.png 39 | 40 | *** Keywords *** 41 | Check Screenshots Before 42 | Set Browser Timeout 3s scope=Suite 43 | Remove File ${OUTPUT DIR}/browser/screenshot/fail-screenshot-1.png 44 | Remove File ${OUTPUT DIR}/browser/screenshot/fail-screenshot-2.png 45 | Remove File ${OUTPUT DIR}/browser/screenshot/fail-screenshot-3.png 46 | Remove File ${OUTPUT DIR}/browser/screenshot/fail-screenshot-4.png 47 | -------------------------------------------------------------------------------- /atest/test/08_Scope_Tests/Suite_1/Suite_1.1/Suite_1.1.1.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../../scope_keywords.resource 3 | 4 | Suite Setup Ensure Open Page ${WAIT_URL_DIRECT} 5 | Test Setup Go To ${WAIT_URL_DIRECT} 6 | Test Timeout 30 sec 7 | 8 | *** Test Cases *** 9 | Test Global Scope And Set Test Scope 10 | Log All Scopes 1000 1000 False ${EMPTY} 11 | Timeout Should Be Between 500 1500 12 | ${global_timeout} = Set Browser Timeout 2s scope=Test 13 | Should Be Equal ${global_timeout} 1 second 14 | Timeout Should Be Between 1500 3000 15 | 16 | Go To ${WAIT_URL_DIRECT} 17 | Strict Mode Should Be False 18 | ${global_strict} = Set Strict Mode ${True} scope=Test 19 | Should Be Equal ${global_strict} ${False} 20 | Strict Mode Should Be True 21 | 22 | Go To ${WAIT_URL_DIRECT} 23 | Assertion Retry Should Be Between 500 1500 24 | ${global_retry} = Set Retry Assertions For 2s scope=Test 25 | Should Be Equal ${global_retry} 1 second 26 | Assertion Retry Should Be Between 1500 3000 27 | 28 | Go To ${WAIT_URL_FRAMED} 29 | ${global_prefix} = Set Selector Prefix ${IFRAME_PREFIX} scope=Test 30 | Should Be Equal ${global_prefix} ${EMPTY} 31 | Strict Mode Should Be True 32 | 33 | Log All Scopes 2000 2000 True ${IFRAME_PREFIX} 34 | 35 | Test Removed Test Scope 36 | Log All Scopes 1000 1000 False ${EMPTY} 37 | Timeout Should Be Between 500 1500 38 | Strict Mode Should Be ${False} 39 | Assertion Retry Should Be Between 500 1500 40 | 41 | Set Suite Level 42 | Set Strict Mode ${True} 43 | Strict Mode Should Be True 44 | Set Browser Timeout 500 ms 45 | Set Retry Assertions For 500 ms 46 | Set Selector Prefix ${IFRAME_PREFIX} 47 | Log All Scopes 500 500 True ${IFRAME_PREFIX} 48 | 49 | Test Suite Level 50 | [Setup] Go To ${WAIT_URL_FRAMED} 51 | Log All Scopes 500 500 True ${IFRAME_PREFIX} 52 | Strict Mode Should Be True 53 | Timeout Should Be Between 100 900 54 | Assertion Retry Should Be Between 100 900 55 | -------------------------------------------------------------------------------- /atest/test/08_Scope_Tests/Suite_1/Suite_1.1/Suite_1.1.2.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../../scope_keywords.resource 3 | 4 | Suite Setup Ensure Open Page ${WAIT_URL_DIRECT} 5 | Test Setup Go To ${WAIT_URL_DIRECT} 6 | 7 | *** Test Cases *** 8 | Test Suite Level Removed 9 | Log All Scopes 1000 1000 False ${EMPTY} 10 | Strict Mode Should Be False 11 | Timeout Should Be Between 500 1500 12 | Assertion Retry Should Be Between 500 1500 13 | 14 | Set Global Scope 15 | Log All Scopes 1000 1000 False ${EMPTY} 16 | Set Browser Timeout 1500 ms scope=Global 17 | Set Retry Assertions For 1500 ms scope=Global 18 | Set Strict Mode True scope=Global 19 | Set Selector Prefix ${IFRAME_PREFIX} scope=Global 20 | Log All Scopes 1500 1500 True ${IFRAME_PREFIX} 21 | -------------------------------------------------------------------------------- /atest/test/08_Scope_Tests/Suite_1/Suite_1.1/Suite_1.1.3.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../../scope_keywords.resource 3 | 4 | Suite Setup Run Keywords New Browser AND New Context 5 | Suite Teardown Close Browser CURRENT 6 | Test Setup New Page ${WAIT_URL_FRAMED} 7 | 8 | *** Test Cases *** 9 | Test Normal Timeout 10 | Select Options By \#dropdown value enabled 11 | Click With Options \#submit noWaitAfter=True 12 | Click "victim" 13 | 14 | Set Timeout To Test Scope 15 | Select Options By \#dropdown value enabled 16 | Click With Options \#submit noWaitAfter=True 17 | Set Browser Timeout 100ms scope=Test 18 | Run Keyword And Expect Error *Timeout 100ms exceeded.* Click "victim" 19 | 20 | Verify Removed Scope 21 | Select Options By \#dropdown value enabled 22 | Click With Options \#submit noWaitAfter=True 23 | Click "victim" 24 | 25 | Set Run On Failure To Test Scope 26 | Register Keyword To Run On Failure LocalStorage Set Item test_name ${TEST_NAME} scope=Test 27 | Run Keyword And Ignore Error Get Title == Wrong Title 28 | LocalStorage Get Item test_name == ${TEST_NAME} 29 | 30 | Check Run On Failure To Test Scope 31 | Run Keyword And Ignore Error Get Title == Wrong Title 32 | LocalStorage Get Item test_name == Set Run On Failure To Test Scope 33 | -------------------------------------------------------------------------------- /atest/test/08_Scope_Tests/Suite_1/Suite_1.2/Suite_1.2.1.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../../scope_keywords.resource 3 | 4 | Suite Setup Ensure Open Page ${WAIT_URL_FRAMED} 5 | 6 | *** Test Cases *** 7 | Test Suite Level Removed 8 | Log All Scopes 1500 1500 True ${IFRAME_PREFIX} 9 | Strict Mode Should Be True 10 | Timeout Should Be Between 1000 2000 11 | Assertion Retry Should Be Between 1000 2000 12 | -------------------------------------------------------------------------------- /atest/test/08_Scope_Tests/Suite_2/Suite_2.1/Suite_2.1.1.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../../scope_keywords.resource 3 | 4 | Suite Setup Ensure Open Page ${WAIT_URL_DIRECT} 5 | Test Setup Go To ${WAIT_URL_DIRECT} 6 | 7 | *** Test Cases *** 8 | Test Suite Level Removed 9 | Log All Scopes 1000 1000 False ${EMPTY} 10 | Strict Mode Should Be False 11 | Timeout Should Be Between 600 1500 12 | Assertion Retry Should Be Between 600 1500 13 | 14 | Altering Levels1 15 | [Setup] New Page about:blank 16 | Set Browser Timeout 3 s Test 17 | Set Browser Timeout 1 s Suite 18 | Set Browser Timeout 2 s Test 19 | Set Browser Timeout 1 s Suite 20 | 21 | Altering Levels2 22 | [Setup] New Page about:blank 23 | Set Browser Timeout 3 s Test 24 | Set Browser Timeout 1 s Suite 25 | Set Browser Timeout 2 s Test 26 | Set Browser Timeout 1 s Suite 27 | Set Browser Timeout 3 s Test 28 | Set Browser Timeout 1 s Suite 29 | Set Browser Timeout 2 s Test 30 | Set Browser Timeout 1 s Suite 31 | Set Browser Timeout 3 s Test 32 | Set Browser Timeout 1 s Suite 33 | Set Browser Timeout 2 s Test 34 | Set Browser Timeout 1 s Suite 35 | -------------------------------------------------------------------------------- /atest/test/08_Scope_Tests/Suite_2/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../scope_keywords.resource 3 | 4 | Suite Setup Set Suite Scope 5 | 6 | *** Keywords *** 7 | Set Suite Scope 8 | Log All Scopes 1500 1500 True ${IFRAME_PREFIX} 9 | Ensure Open Page ${WAIT_URL_DIRECT} 10 | Set Browser Timeout 1 sec 11 | Set Retry Assertions For 1 sec 12 | Set Strict Mode False 13 | Set Selector Prefix ${EMPTY} 14 | Log All Scopes 1000 1000 False ${EMPTY} 15 | -------------------------------------------------------------------------------- /atest/test/08_Scope_Tests/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource scope_keywords.resource 3 | 4 | Suite Setup Set New Global Settings 5 | Suite Teardown Reset Global Settings 6 | 7 | *** Variables *** 8 | ${org_timeout} = ${None} 9 | ${org_retry} = ${None} 10 | ${org_strict} = ${None} 11 | ${org_prefix} = ${None} 12 | 13 | *** Keywords *** 14 | Close All And Open New Browser 15 | Close Browser ALL 16 | Set New Global Settings 17 | 18 | Set New Global Settings 19 | ${org_timeout} = Set Browser Timeout 1000 ms scope=global 20 | Set Global Variable $org_timeout 21 | ${org_retry} = Set Retry Assertions For 1000 ms scope=global 22 | Set Global Variable $org_retry 23 | ${org_strict} = Set Strict Mode False scope=global 24 | Set Global Variable $org_strict 25 | ${org_prefix} = Set Selector Prefix ${EMPTY} 26 | Set Global Variable $org_prefix 27 | 28 | Log All Scopes 1000 1000 False ${EMPTY} 29 | 30 | Reset Global Settings 31 | Set Browser Timeout ${org_timeout} scope=global 32 | Set Retry Assertions For ${org_retry} scope=global 33 | Set Strict Mode ${org_strict} scope=global 34 | Set Selector Prefix ${org_prefix} scope=global 35 | -------------------------------------------------------------------------------- /atest/test/08_Scope_Tests/scope_logger.py: -------------------------------------------------------------------------------- 1 | from robot.libraries.BuiltIn import BuiltIn 2 | from robot.api import logger 3 | from Browser import Browser 4 | from typing import Optional 5 | 6 | 7 | def log_all_scopes( 8 | exp_timeout: float, 9 | exp_retry_assertions_for: float, 10 | exp_strict_mode: bool, 11 | exp_selector_prefix: Optional[str] = None, 12 | ): 13 | b: Browser = BuiltIn().get_library_instance("Browser") 14 | timeout = b.scope_stack["timeout"].get() 15 | retry_assertions_for = b.scope_stack["retry_assertions_for"].get() 16 | strict_mode = b.scope_stack["strict_mode"].get() 17 | selector_prefix = b.scope_stack["selector_prefix"].get() 18 | 19 | assert ( 20 | timeout == exp_timeout 21 | ), f"timeout: {timeout} ({type(timeout)}) != {exp_timeout} ({type(exp_timeout)})" 22 | assert ( 23 | retry_assertions_for == exp_retry_assertions_for 24 | ), f"retry_assertions_for: {retry_assertions_for} ({type(retry_assertions_for)}) != {exp_retry_assertions_for} ({type(exp_retry_assertions_for)})" 25 | assert ( 26 | strict_mode == exp_strict_mode 27 | ), f"strict_mode: {strict_mode} ({type(strict_mode)}) != {exp_strict_mode} ({type(exp_strict_mode)})" 28 | assert ( 29 | selector_prefix == exp_selector_prefix 30 | ), f"selector_prefix: {selector_prefix} ({type(selector_prefix)}) != {exp_selector_prefix} ({type(exp_selector_prefix)})" 31 | 32 | logger.info(f"timeout: {timeout}") 33 | logger.info(f"retry_assertions_for: {retry_assertions_for}") 34 | logger.info(f"strict_mode: {strict_mode}") 35 | logger.info(f"selector_prefix: {selector_prefix}") 36 | 37 | return { 38 | "timeout": timeout, 39 | "retry_assertions_for": retry_assertions_for, 40 | "strict_mode": strict_mode, 41 | "selector_prefix": selector_prefix, 42 | } 43 | -------------------------------------------------------------------------------- /atest/test/09_Plugins/ExamplePlugin.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from robot.api import logger 5 | from robot.api.deco import keyword 6 | from robot.utils import DotDict 7 | 8 | from Browser import Browser 9 | from Browser.base.librarycomponent import LibraryComponent 10 | from Browser.generated.playwright_pb2 import Request 11 | from Browser.utils import SettingsStack, Scope 12 | 13 | 14 | class ExamplePlugin(LibraryComponent): 15 | ROBOT_LISTENER_API_VERSION = 2 16 | 17 | def __init__(self, library: Browser): 18 | super().__init__(library) 19 | self.initialize_js_extension(Path(__file__).parent.resolve() / "jsplugin.js") 20 | library.scope_stack["last_log_message"] = SettingsStack("", library) 21 | 22 | def end_keyword(self, _kw, _args): 23 | msg = self.library.scope_stack["last_log_message"].get() 24 | if msg: 25 | logger.info(msg) 26 | 27 | @keyword 28 | def set_last_log_message(self, msg: str, scope: Scope): 29 | self.library.scope_stack["last_log_message"].set(msg, scope) 30 | 31 | @keyword 32 | def new_plugin_cookie_keyword(self) -> dict: 33 | """Uses grpc to directly call node side function.""" 34 | with self.playwright.grpc_channel() as stub: 35 | response = stub.GetCookies(Request().Empty()) 36 | cookies = json.loads(response.json) 37 | assert len(cookies) == 1, "Too many cookies." 38 | return {"name": cookies[0]["name"], "value": cookies[0]["value"]} 39 | 40 | @keyword 41 | def get_location_object(self) -> dict: 42 | """Returns the location object of the current page. 43 | 44 | This keyword calles the python keyword `Evaluate Javascript` to get the location object. 45 | """ 46 | location_dict = self.library.evaluate_javascript(None, f"window.location") 47 | logger.info(f"Location object:\n {json.dumps(location_dict, indent=2)}") 48 | return DotDict(location_dict) 49 | 50 | @keyword 51 | def mouse_wheel(self, x: int, y: int): 52 | """This keyword calls a custom javascript keyword from the file jsplugin.js.""" 53 | return self.call_js_keyword("mouseWheel", y=y, x=x) 54 | -------------------------------------------------------------------------------- /atest/test/09_Plugins/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../variables.resource 3 | Resource ../keywords.resource 4 | -------------------------------------------------------------------------------- /atest/test/09_Plugins/jsplugin.js: -------------------------------------------------------------------------------- 1 | async function mouseWheel(x, y, logger, page) { 2 | logger(`Mouse wheel at ${x}, ${y}`); 3 | await page.mouse.wheel(x, y); 4 | logger("Returning a funny string"); 5 | return await page.evaluate("document.scrollingElement.scrollTop"); 6 | } 7 | 8 | exports.__esModule = true; 9 | exports.mouseWheel = mouseWheel; 10 | -------------------------------------------------------------------------------- /atest/test/10_retest/mylib.py: -------------------------------------------------------------------------------- 1 | from robot.libraries.BuiltIn import BuiltIn 2 | from pathlib import Path 3 | from Browser import Browser 4 | 5 | 6 | def create_context_with_string_type_recordvideodir_and_get_type_of_recordVideo_dir( 7 | video_path, 8 | ): 9 | browser: Browser = BuiltIn().get_library_instance("Browser") 10 | record_video = {"dir": video_path} 11 | browser.new_context(recordVideo=record_video) 12 | url = BuiltIn().get_variable_value("${LOGIN_URL}") 13 | browser.new_page(url) 14 | return str(type(record_video["dir"])) 15 | 16 | 17 | def create_context_with_path_type_recordvideodir_and_get_type_of_recordVideo_dir( 18 | video_path, 19 | ): 20 | browser: Browser = BuiltIn().get_library_instance("Browser") 21 | record_video = {"dir": Path(video_path)} 22 | browser.new_context(recordVideo=record_video) 23 | url = BuiltIn().get_variable_value("${LOGIN_URL}") 24 | browser.new_page(url) 25 | return str(type(record_video["dir"])) 26 | 27 | 28 | def create_persistent_context_with_string_type_recordvideodir_and_get_type_of_recordVideo_dir( 29 | video_path, 30 | ): 31 | browser: Browser = BuiltIn().get_library_instance("Browser") 32 | record_video = {"dir": video_path} 33 | url = BuiltIn().get_variable_value("${LOGIN_URL}") 34 | browser.new_persistent_context(recordVideo=record_video, url=url) 35 | return str(type(record_video["dir"])) 36 | 37 | 38 | def create_persistent_context_with_path_type_recordvideodir_and_get_type_of_recordVideo_dir( 39 | video_path, 40 | ): 41 | browser: Browser = BuiltIn().get_library_instance("Browser") 42 | record_video = {"dir": Path(video_path)} 43 | url = BuiltIn().get_variable_value("${LOGIN_URL}") 44 | browser.new_persistent_context(recordVideo=record_video, url=url) 45 | return str(type(record_video["dir"])) 46 | -------------------------------------------------------------------------------- /atest/test/11_tidy_transformer/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../variables.resource 3 | Resource ../keywords.resource 4 | -------------------------------------------------------------------------------- /atest/test/11_tidy_transformer/network_idle_file.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Test Tags tidy-transformer 3 | 4 | *** Test Cases *** 5 | Parsing Wait Until Network Is Idle To Wait For Load State 6 | ${not used} = New Context # This is not changed 7 | Wait Until Network Is Idle timeout=0.1s 8 | Wait Until Network Is Idle 9 | Wait Until Network Is Idle timeout=0.1s # Comment should be preserved 10 | Browser.Wait Until Network Is Idle timeout=1.2s # Comment should be preserved 11 | ${not used} = New Context # This is not changed 12 | -------------------------------------------------------------------------------- /atest/test/11_tidy_transformer/network_idle_test.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource imports.resource 3 | 4 | *** Test Cases *** 5 | Tranform Wait Until Network Is Idle Keyword 6 | [Setup] Network Idle Setup 7 | ${entry_cmd} = Get Enty Command 8 | ${process} = Run Process 9 | ... ${entry_cmd} transform --wait-until-network-is-idle ${CURDIR}/network_idle_file.robot 10 | ... shell=True 11 | Log ${process.stdout} 12 | Log ${process.stderr} 13 | Should Be Equal As Integers ${process.rc} 0 14 | ${file} = Get File ${CURDIR}/network_idle_file.robot 15 | ${lines} = Split To Lines ${file} 16 | Log ${lines} 17 | Should Be Equal ${lines}[5] ${SPACE*4}\${not used} =${SPACE*2}New Context${SPACE*4}\# This is not changed 18 | Should Be Equal ${lines}[6] ${SPACE*4}Wait For Load State${SPACE*4}networkidle${SPACE*4}timeout=0.1s 19 | Should Be Equal ${lines}[7] ${SPACE*4}Wait For Load State${SPACE*4}networkidle 20 | Should Be Equal 21 | ... ${lines}[8] 22 | ... ${SPACE*4}Wait For Load State${SPACE*4}networkidle${SPACE*4}timeout=0.1s${SPACE*4}# Comment should be preserved 23 | Should Be Equal 24 | ... ${lines}[9] 25 | ... ${SPACE*4}Browser.Wait For Load State${SPACE*4}networkidle${SPACE*4}timeout=1.2s${SPACE*4}# Comment should be preserved 26 | Should Be Equal ${lines}[10] ${SPACE*4}\${not used} =${SPACE*7}New Context${SPACE*4}# This is not changed 27 | Length Should Be ${lines} 11 28 | [Teardown] Network Idle Treardown 29 | 30 | *** Keywords *** 31 | Network Idle Setup 32 | ${NETWORK_IDLE_FILE_DATA} = Get File ${CURDIR}/network_idle_file.robot 33 | Set Test Variable ${NETWORK_IDLE_FILE_DATA} 34 | 35 | Network Idle Treardown 36 | Create File ${CURDIR}/network_idle_file.robot ${NETWORK_IDLE_FILE_DATA} 37 | -------------------------------------------------------------------------------- /atest/test/11_tidy_transformer/readme.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | This folder contains test which are used to tidy parsing. It contains test data, 3 | which is not executed by `inv atest`, because test data transformmed by tidy and 4 | library internal transformers. Example `Wait Until Network Is Idle` keyword 5 | is transformmer to `Wait For Load State` keyword. 6 | 7 | # Naming convension 8 | Test data file which is transformed by tidy is names by: 9 | _file.robot. File which tests the transformer 10 | is named by: _test.robot. Example: 11 | `network_idle_file.robot` is transformed by tidy 12 | and `network_idle_test.robot` is the test which transforms the test 13 | file. 14 | 15 | # Excluding 16 | The test is exluded by adding `tidy-transformer` tag to the 17 | _file.robot file. 18 | -------------------------------------------------------------------------------- /atest/test/12_rfbrowser/SomePlugin.py: -------------------------------------------------------------------------------- 1 | from robot.api.deco import keyword 2 | 3 | from Browser.base.librarycomponent import LibraryComponent 4 | 5 | 6 | class SomePlugin(LibraryComponent): 7 | 8 | @keyword 9 | def this_is_plugin_keyword(self): 10 | """Docs""" 11 | return 1 12 | -------------------------------------------------------------------------------- /atest/test/12_rfbrowser/imports.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ../variables.resource 3 | Resource ../keywords.resource 4 | -------------------------------------------------------------------------------- /atest/test/12_rfbrowser/jsplugin.js: -------------------------------------------------------------------------------- 1 | async function mouseWheelAsPlugin(x, y, logger, page) { 2 | logger(`Mouse wheel at ${x}, ${y}`); 3 | await page.mouse.wheel(x, y); 4 | logger("Returning a funny string"); 5 | return await page.evaluate("document.scrollingElement.scrollTop"); 6 | } 7 | 8 | exports.__esModule = true; 9 | exports.mouseWheelAsPlugin = mouseWheelAsPlugin; 10 | -------------------------------------------------------------------------------- /atest/test/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library pabot.SharedLibrary Process 3 | Library pabot.PabotLib 4 | Library ../library/common.py 5 | Resource variables.resource 6 | Resource keywords.resource 7 | 8 | Suite Setup Start Test Application 9 | Suite Teardown Suite Teardown 10 | Test Timeout ${DEFAULT_TEST_TIMEOUT} 11 | 12 | *** Keywords *** 13 | Start Test Application 14 | ${port} = Start Test Server 15 | Set Global Variable $SERVER_PORT ${port} 16 | 17 | Suite Teardown 18 | Stop Test Server ${SERVER_PORT} 19 | Suite Cleanup 20 | -------------------------------------------------------------------------------- /bootstrap.py: -------------------------------------------------------------------------------- 1 | """Creates a virtual environment for developing the library. 2 | 3 | Also installs the needed dependencies. 4 | """ 5 | 6 | import platform 7 | import subprocess 8 | from pathlib import Path 9 | from venv import EnvBuilder 10 | 11 | venv_dir = Path(__file__).parent / ".venv" 12 | if not platform.platform().startswith("Windows"): 13 | venv_python = venv_dir / "bin" / "python" 14 | else: 15 | venv_python = venv_dir / "Scripts" / "python.exe" 16 | src_dir = Path(__file__).parent / "Browser" 17 | 18 | if not venv_dir.exists(): 19 | print(f"Creating virtualenv in {venv_dir}") 20 | EnvBuilder(with_pip=True).create(venv_dir) 21 | 22 | subprocess.run([venv_python, "-m", "pip", "install", "-U", "uv"], check=True) 23 | subprocess.run( 24 | [ 25 | venv_python, 26 | "-m", 27 | "uv", 28 | "pip", 29 | "install", 30 | "-r", 31 | str(src_dir / "dev-requirements.txt"), 32 | ], 33 | check=True, 34 | ) 35 | 36 | activate_script = ( 37 | "source .venv/bin/activate" 38 | if not platform.platform().startswith("Windows") 39 | else ".venv\\Scripts\\activate.bat" 40 | ) 41 | print(f"Virtualenv `{venv_dir}` is ready and up-to-date.") 42 | print(f"Run `{activate_script}` to activate the virtualenv.") 43 | -------------------------------------------------------------------------------- /docker/Dockerfile.latest_release: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/playwright:v1.52.0-noble 2 | 3 | # Update apt-get 4 | USER root 5 | RUN apt-get update 6 | RUN python3 --version 7 | RUN apt install -y python3-pip python3.12-venv 8 | 9 | # Clean up 10 | USER root 11 | RUN apt-get clean && \ 12 | rm -rf /var/lib/apt/lists/* 13 | 14 | # Set environment variables 15 | ENV PATH="/home/pwuser/.local/bin:${PATH}" 16 | ENV NODE_PATH=/usr/lib/node_modules 17 | 18 | # Cache operations 19 | USER root 20 | RUN mv /root/.cache/ /home/pwuser/.cache || true 21 | RUN chmod a+rwx -R /home/pwuser/.cache || true 22 | 23 | # Switch to pwuser for the remaining operations 24 | USER pwuser 25 | 26 | # Create venv and active it 27 | RUN python3 -m venv /home/pwuser/.venv 28 | ENV PATH="/home/pwuser/.venv/bin:$PATH" 29 | 30 | # Upgrade pip and wheel for the user 31 | RUN pip3 install --no-cache-dir --upgrade pip wheel uv 32 | 33 | # Install RobotFramework and Browser library 34 | RUN uv pip install --no-cache-dir --upgrade robotframework robotframework-browser==19.5.1 35 | 36 | # Initialize Browser library without browsers binaries 37 | RUN python3 -m Browser.entry init --skip-browsers 38 | -------------------------------------------------------------------------------- /docker/Dockerfile.tests: -------------------------------------------------------------------------------- 1 | FROM marketsquare/robotframework-browser:latest AS StableTester 2 | 3 | USER pwuser 4 | 5 | WORKDIR /app 6 | COPY Browser/dev-requirements.txt dev-requirements.txt 7 | # the base image has the stable browser, so we don't want to double install it's deps 8 | RUN touch requirements.txt 9 | RUN pip3 install --no-cache-dir --upgrade pip wheel 10 | RUN pip3 install --no-cache-dir -r ./dev-requirements.txt 11 | COPY tasks.py tasks.py 12 | -------------------------------------------------------------------------------- /docker/atest/test.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Browser 3 | 4 | *** Test Cases *** 5 | Simple 6 | New Page https://github.com/MarketSquare/robotframework-browser/ 7 | Close Page 8 | -------------------------------------------------------------------------------- /docs/examples/babelES2015/README.md: -------------------------------------------------------------------------------- 1 | # robotframework-browser 2 | 3 | This example shows how to translate more modern ECMAScript 2015+ javascript modules to CommonJS. 4 | The example uses [Babel](https://babeljs.io/) to translate es2015 `index.js` file in `src` folder to CommonJS file in lib `folder`. 5 | 6 | To run the example translation and to show the relevant documentation do: 7 | ``` 8 | npm install 9 | npm run build 10 | python -m robot.libdoc Browser::jsextension=lib/index.js show funkkari 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/examples/babelES2015/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/babelES2015/lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.funkkari = void 0; 7 | 8 | const funkkari = (argumentti, argu2, argu3 = 3, // hello 9 | huu = 'hello') => { 10 | return 123; 11 | }; 12 | 13 | exports.funkkari = funkkari; 14 | funkkari.rfdoc = ` 15 | Hello from new javascript 16 | `; -------------------------------------------------------------------------------- /docs/examples/babelES2015/package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarketSquare/robotframework-browser/e98197faab8e0f18491ab8eed938a472dc8e8bbe/docs/examples/babelES2015/package -------------------------------------------------------------------------------- /docs/examples/babelES2015/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babelES2015", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "babel src -d lib" 6 | }, 7 | "author": "", 8 | "license": "ISC", 9 | "devDependencies": { 10 | "@babel/cli": "^7.16.0", 11 | "@babel/core": "^7.16.0", 12 | "@babel/plugin-transform-modules-commonjs": "^7.16.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/examples/babelES2015/src/index.js: -------------------------------------------------------------------------------- 1 | export const funkkari = (argumentti, argu2, 2 | argu3=3, // hello 3 | huu='hello') => { 4 | return 123; 5 | } 6 | funkkari.rfdoc = ` 7 | Hello from new javascript 8 | ` 9 | -------------------------------------------------------------------------------- /docs/plugins/example/ExamplePlugin.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from robot.api.deco import keyword # Decorator to mark which methods are keyword 4 | 5 | from Browser.base.librarycomponent import LibraryComponent # Plugin class must always inherit LibraryComponent 6 | from Browser.generated.playwright_pb2 import Request # GRPC stub which is used to talk Playwright running in node side 7 | 8 | 9 | class ExamplePlugin(LibraryComponent): # Inherit LibraryComponent 10 | 11 | @keyword # This method is keyword 12 | def new_plugin_cookie_keyword(self) -> dict: 13 | with self.playwright.grpc_channel() as stub: # Open grpc channel. 14 | response = stub.GetCookies(Request().Empty()) # Call the cookies implementation from Node side. 15 | cookies = json.loads(response.json) # Get json payload from response. 16 | # Write Python code as you need, these lines are just examples. 17 | for cookie in cookies: 18 | if cookie["name"] == "Foo22": 19 | return { 20 | "name": cookie["name"], 21 | "value": cookie["value"] 22 | } 23 | return {} 24 | 25 | def now_keyword(self): # This is not keyword 26 | print(1) 27 | -------------------------------------------------------------------------------- /docs/plugins/example/test.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library Browser plugins=${CURDIR}/ExamplePlugin.py 3 | Test Setup New Page https://www.google.com/ 4 | 5 | *** Test Cases *** 6 | Example 7 | ${url} = Get Url 8 | Add Cookie 9 | ... Foo22 10 | ... Bar22 11 | ... url=${url} 12 | ... expires=3 155 760 000,195223 13 | ${cookies} = New Plugin Cookie Keyword 14 | Should Not Be Empty ${cookies} 15 | Should Be Equal ${cookies}[name] Foo22 16 | Should Be Equal ${cookies}[value] Bar22 17 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-16.0.2.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Browser library 16.0.2 3 | ====================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 16.0.2 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v16.0.2 can be found 13 | from the `issue tracker`_. 14 | For first time installation with pip_, just run 15 | :: 16 | pip install robotframework-browser 17 | rfbrowser init 18 | to install the latest available release. If you upgrading 19 | from previous release with pip_, run 20 | :: 21 | pip install --upgrade robotframework-browser 22 | rfbrowser clean-node 23 | rfbrowser init 24 | Alternatively you can download the source distribution from PyPI_ and 25 | install it manually. Browser library 16.0.2 was released on Wednesday March 15, 2023. 26 | Browser supports Python 3.7+, Node 14/16 LTS and Robot Framework 4.0+. 27 | Library was tested with Playwright 1.31.2 28 | 29 | .. _Robot Framework: http://robotframework.org 30 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 31 | .. _Playwright: https://github.com/microsoft/playwright 32 | .. _pip: http://pip-installer.org 33 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 34 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones/v16.0.2 35 | 36 | 37 | .. contents:: 38 | :depth: 2 39 | :local: 40 | 41 | Full list of fixes and enhancements 42 | =================================== 43 | 44 | .. list-table:: 45 | :header-rows: 1 46 | 47 | * - ID 48 | - Type 49 | - Priority 50 | - Summary 51 | * - `#2701`_ 52 | - --- 53 | - --- 54 | - Docker image is not pushing other than latest tags correctly 55 | 56 | Altogether 1 issue. View on the `issue tracker `__. 57 | 58 | .. _#2701: https://github.com/MarketSquare/robotframework-browser/issues/2701 59 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-16.0.4.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Browser library 16.0.4 3 | ====================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 16.0.4 is a new release with 11 | bug fixes to rfbroser init. All issues targeted for Browser library v16.0.4 12 | can be found from the `issue tracker`_. 13 | For first time installation with pip_, just run 14 | :: 15 | pip install robotframework-browser 16 | rfbrowser init 17 | to install the latest available release. If you upgrading 18 | from previous release with pip_, run 19 | :: 20 | pip install --upgrade robotframework-browser 21 | rfbrowser clean-node 22 | rfbrowser init 23 | Alternatively you can download the source distribution from PyPI_ and 24 | install it manually. Browser library 16.0.4 was released on Sunday April 30, 2023. 25 | Browser supports Python 3.7+, Node 14/16 LTS and Robot Framework 4.0+. 26 | Library was tested with Playwright 1.33.0 27 | 28 | .. _Robot Framework: http://robotframework.org 29 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 30 | .. _Playwright: https://github.com/microsoft/playwright 31 | .. _pip: http://pip-installer.org 32 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 33 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones/v16.0.4 34 | 35 | 36 | .. contents:: 37 | :depth: 2 38 | :local: 39 | 40 | Full list of fixes and enhancements 41 | =================================== 42 | 43 | .. list-table:: 44 | :header-rows: 1 45 | 46 | * - ID 47 | - Type 48 | - Priority 49 | - Summary 50 | * - `#2727`_ 51 | - bug 52 | - medium 53 | - Teamcity build fails during installation rfbrowser node dependencies with UnicodeEncodeError 54 | 55 | Altogether 1 issue. View on the `issue tracker `__. 56 | 57 | .. _#2727: https://github.com/MarketSquare/robotframework-browser/issues/2727 58 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-17.2.0.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Browser library 17.2.0 3 | ====================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 17.2.0 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v17.2.0 can be found 13 | from the `issue tracker`_. 14 | For first time installation with pip_, just run 15 | :: 16 | pip install robotframework-browser 17 | rfbrowser init 18 | to install the latest available release. If you upgrading 19 | from previous release with pip_, run 20 | :: 21 | pip install --upgrade robotframework-browser 22 | rfbrowser clean-node 23 | rfbrowser init 24 | Alternatively you can download the source distribution from PyPI_ and 25 | install it manually. Browser library 17.2.0 was released on Friday July 28, 2023. 26 | Browser supports Python 3.8+, Node 16/18 LTS and Robot Framework 5.0+. 27 | Library was tested with Playwright 1.36.2 28 | 29 | .. _Robot Framework: http://robotframework.org 30 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 31 | .. _Playwright: https://github.com/microsoft/playwright 32 | .. _pip: http://pip-installer.org 33 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 34 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones/v17.2.0 35 | 36 | 37 | .. contents:: 38 | :depth: 2 39 | :local: 40 | 41 | Full list of fixes and enhancements 42 | =================================== 43 | 44 | .. list-table:: 45 | :header-rows: 1 46 | 47 | * - ID 48 | - Type 49 | - Priority 50 | - Summary 51 | * - `#2987`_ 52 | - enhancement 53 | - --- 54 | - Set FirefoxUserPrefs and other missing args in `New Browser` Keyword 55 | 56 | Altogether 1 issue. View on the `issue tracker `__. 57 | 58 | .. _#2987: https://github.com/MarketSquare/robotframework-browser/issues/2987 59 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-17.4.0.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Browser library 17.4.0 3 | ====================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 17.4.0 is a new release with 11 | enhancements Wait For Download keyword to better support remote browser. 12 | All issues targeted for Browser library v17.4.0 can be found 13 | from the `issue tracker`_. 14 | For first time installation with pip_, just run 15 | :: 16 | pip install robotframework-browser 17 | rfbrowser init 18 | to install the latest available release. If you upgrading 19 | from previous release with pip_, run 20 | :: 21 | pip install --upgrade robotframework-browser 22 | rfbrowser clean-node 23 | rfbrowser init 24 | Alternatively you can download the source distribution from PyPI_ and 25 | install it manually. Browser library 17.4.0 was released on Friday September 1, 2023. 26 | Browser supports Python 3.8+, Node 16/18 LTS and Robot Framework 5.0+. 27 | Library was tested with Playwright 1.37.1 28 | 29 | .. _Robot Framework: http://robotframework.org 30 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 31 | .. _Playwright: https://github.com/microsoft/playwright 32 | .. _pip: http://pip-installer.org 33 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 34 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones/v17.4.0 35 | 36 | 37 | .. contents:: 38 | :depth: 2 39 | :local: 40 | 41 | Full list of fixes and enhancements 42 | =================================== 43 | 44 | .. list-table:: 45 | :header-rows: 1 46 | 47 | * - ID 48 | - Type 49 | - Priority 50 | - Summary 51 | * - `#2615`_ 52 | - bug 53 | - medium 54 | - Wait For Download always uses path() and is thus unusable for remote playwright 55 | 56 | Altogether 1 issue. View on the `issue tracker `__. 57 | 58 | .. _#2615: https://github.com/MarketSquare/robotframework-browser/issues/2615 59 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-18.5.1.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Browser library 18.5.1 3 | ====================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 18.5.1 is a new release with 11 | bug fixes to docker container release. All issues targeted for Browser library 12 | v18.5.1 can be found from the `issue tracker`_. For first time installation 13 | with pip_, just run 14 | :: 15 | pip install robotframework-browser 16 | rfbrowser init 17 | to install the latest available release. If you upgrading 18 | from previous release with pip_, run 19 | :: 20 | pip install --upgrade robotframework-browser 21 | rfbrowser clean-node 22 | rfbrowser init 23 | Alternatively you can download the source distribution from PyPI_ and 24 | install it manually. Browser library 18.5.1 was released on Thursday May 16, 2024. 25 | Browser supports Python 3.8+, Node 18/20 LTS and Robot Framework 5.0+. 26 | Library was tested with Playwright 1.44.0 27 | 28 | .. _Robot Framework: http://robotframework.org 29 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 30 | .. _Playwright: https://github.com/microsoft/playwright 31 | .. _pip: http://pip-installer.org 32 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 33 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones/v18.5.1 34 | 35 | 36 | .. contents:: 37 | :depth: 2 38 | :local: 39 | 40 | Full list of fixes and enhancements 41 | =================================== 42 | 43 | .. list-table:: 44 | :header-rows: 1 45 | 46 | * - ID 47 | - Type 48 | - Priority 49 | - Summary 50 | * - `#3603`_ 51 | - --- 52 | - --- 53 | - Fix creation of docker image 54 | 55 | Altogether 1 issue. View on the `issue tracker `__. 56 | 57 | .. _#3603: https://github.com/MarketSquare/robotframework-browser/issues/3603 58 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-18.6.1.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Browser library 18.6.1 3 | ====================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 18.6.1 is a new release with 11 | bug fixe to Docker creation. All issues targeted for Browser library 12 | v18.6.1 can be found from the `issue tracker`_. For first time installation 13 | with pip_, just run 14 | :: 15 | pip install robotframework-browser 16 | rfbrowser init 17 | to install the latest available release. If you upgrading 18 | from previous release with pip_, run 19 | :: 20 | pip install --upgrade robotframework-browser 21 | rfbrowser clean-node 22 | rfbrowser init 23 | Alternatively you can download the source distribution from PyPI_ and 24 | install it manually. Browser library 18.6.1 was released on Saturday June 22, 2024. 25 | Browser supports Python 3.8+, Node 18/20 LTS and Robot Framework 5.0+. 26 | Library was tested with Playwright 1.44.1 27 | 28 | .. _Robot Framework: http://robotframework.org 29 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 30 | .. _Playwright: https://github.com/microsoft/playwright 31 | .. _pip: http://pip-installer.org 32 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 33 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones/v18.6.1 34 | 35 | 36 | .. contents:: 37 | :depth: 2 38 | :local: 39 | 40 | Most important enhancements 41 | =========================== 42 | 43 | Docker release got broken (`#3663`_) 44 | ------------------------------------ 45 | Docker image release had bug and that is now fixed. 46 | 47 | Full list of fixes and enhancements 48 | =================================== 49 | 50 | .. list-table:: 51 | :header-rows: 1 52 | 53 | * - ID 54 | - Type 55 | - Priority 56 | - Summary 57 | * - `#3663`_ 58 | - bug 59 | - critical 60 | - Docker release got broken 61 | 62 | Altogether 1 issue. View on the `issue tracker `__. 63 | 64 | .. _#3663: https://github.com/MarketSquare/robotframework-browser/issues/3663 65 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-18.6.2.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Browser library 18.6.2 3 | ====================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 18.6.2 is a new release with 11 | bug fix again for docer release. All issues targeted for Browser library 12 | v18.6.2 can be found from the `issue tracker`_. 13 | For first time installation with pip_, just run 14 | :: 15 | pip install robotframework-browser 16 | rfbrowser init 17 | to install the latest available release. If you upgrading 18 | from previous release with pip_, run 19 | :: 20 | pip install --upgrade robotframework-browser 21 | rfbrowser clean-node 22 | rfbrowser init 23 | Alternatively you can download the source distribution from PyPI_ and 24 | install it manually. Browser library 18.6.2 was released on Saturday June 22, 2024. 25 | Browser supports Python 3.8+, Node 18/20 LTS and Robot Framework 5.0+. 26 | Library was tested with Playwright 1.44.1 27 | 28 | .. _Robot Framework: http://robotframework.org 29 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 30 | .. _Playwright: https://github.com/microsoft/playwright 31 | .. _pip: http://pip-installer.org 32 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 33 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones/v18.6.2 34 | 35 | 36 | .. contents:: 37 | :depth: 2 38 | :local: 39 | 40 | Most important enhancements 41 | =========================== 42 | 43 | **EXPLAIN** or remove these. 44 | 45 | - Docker release got broken (`#3663`_) 46 | 47 | Full list of fixes and enhancements 48 | =================================== 49 | 50 | .. list-table:: 51 | :header-rows: 1 52 | 53 | * - ID 54 | - Type 55 | - Priority 56 | - Summary 57 | * - `#3663`_ 58 | - bug 59 | - critical 60 | - Docker release got broken 61 | 62 | Altogether 1 issue. View on the `issue tracker `__. 63 | 64 | .. _#3663: https://github.com/MarketSquare/robotframework-browser/issues/3663 65 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-2.3.1.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 2.3.1 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 2.3.1 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v2.3.1 can be found 13 | from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==2.3.1 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 2.3.1 was released on Wednesday November 25, 2020. Browser supports 25 | Python **>=3.7**, and Robot Framework **>=3.2**. 26 | 27 | .. _Robot Framework: http://robotframework.org 28 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 29 | .. _Playwright: https://github.com/microsoft/playwright 30 | .. _pip: http://pip-installer.org 31 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 32 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av2.3.1 33 | 34 | 35 | .. contents:: 36 | :depth: 2 37 | :local: 38 | 39 | Full list of fixes and enhancements 40 | =================================== 41 | 42 | .. list-table:: 43 | :header-rows: 1 44 | 45 | * - ID 46 | - Type 47 | - Priority 48 | - Summary 49 | * - `#523`_ 50 | - --- 51 | - --- 52 | - Install browsers and other required data all in site-packages/Browser/wrapper instead of in users home dir 53 | 54 | Altogether 1 issue. View on the `issue tracker `__. 55 | 56 | .. _#523: https://github.com/MarketSquare/robotframework-browser/issues/523 57 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-2.3.4.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 2.3.4 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 2.3.4 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v2.3.4 can be found 13 | from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==2.3.4 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 2.3.4 was released on Monday December 7, 2020. Browser supports 25 | Python **>=3.7**, and Robot Framework **>=3.2**. 26 | 27 | .. _Robot Framework: http://robotframework.org 28 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 29 | .. _Playwright: https://github.com/microsoft/playwright 30 | .. _pip: http://pip-installer.org 31 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 32 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av2.3.4 33 | 34 | 35 | .. contents:: 36 | :depth: 2 37 | :local: 38 | 39 | Full list of fixes and enhancements 40 | =================================== 41 | 42 | .. list-table:: 43 | :header-rows: 1 44 | 45 | * - ID 46 | - Type 47 | - Priority 48 | - Summary 49 | * - `#531`_ 50 | - --- 51 | - --- 52 | - Segmentation fault: 11 installing on Mac OS X 10.15.7 53 | * - `#543`_ 54 | - --- 55 | - --- 56 | - Installation of robotframework-browser fails 57 | 58 | Altogether 2 issues. View on the `issue tracker `__. 59 | 60 | .. _#531: https://github.com/MarketSquare/robotframework-browser/issues/531 61 | .. _#543: https://github.com/MarketSquare/robotframework-browser/issues/543 62 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-2.4.1.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 2.4.1 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 2.4.1 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v2.4.1 can be found 13 | from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==2.4.1 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 2.4.1 was released on Friday December 18, 2020. Browser supports 25 | Python **>=3.7**, and Robot Framework **>=3.2**. 26 | 27 | .. _Robot Framework: http://robotframework.org 28 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 29 | .. _Playwright: https://github.com/microsoft/playwright 30 | .. _pip: http://pip-installer.org 31 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 32 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av2.4.1 33 | 34 | 35 | .. contents:: 36 | :depth: 2 37 | :local: 38 | 39 | Full list of fixes and enhancements 40 | =================================== 41 | 42 | .. list-table:: 43 | :header-rows: 1 44 | 45 | * - ID 46 | - Type 47 | - Priority 48 | - Summary 49 | * - `#600`_ 50 | - --- 51 | - --- 52 | - Fix macOS 11.1 by depending on newer playwright version 53 | * - 54 | - --- 55 | - --§ 56 | - New Playwright CSS selector extensions and other features from 1.7.0 https://github.com/microsoft/playwright/releases/tag/v1.7.0 57 | 58 | Altogether 1 issue. View on the `issue tracker `__. 59 | 60 | .. _#600: https://github.com/MarketSquare/robotframework-browser/issues/600 61 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-3.1.0.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 3.1.0 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 3.1.0 is a new release with 11 | with Playwright 1.8.0. All issues targeted for Browser library v3.1.0 can be found 12 | from the `issue tracker`_. 13 | If you have pip_ installed, just run 14 | :: 15 | pip install --upgrade robotframework-browser 16 | rfbrowser init 17 | to install the latest available release or use 18 | :: 19 | pip install robotframework-browser==3.1.0 20 | rfbrowser init 21 | 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 3.1.0 was released on Thursday January 21, 2021. Browser supports 25 | Python **>=3.7**, and Robot Framework **>=3.2**. 26 | 27 | .. _Robot Framework: http://robotframework.org 28 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 29 | .. _Playwright: https://github.com/microsoft/playwright 30 | .. _pip: http://pip-installer.org 31 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 32 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av3.1.0 33 | 34 | 35 | .. contents:: 36 | :depth: 2 37 | :local: 38 | 39 | Most important enhancements 40 | =========================== 41 | 42 | Update to latest Playwright (`#681`_) 43 | ------------------------------------- 44 | This release is same as 3.0.0, but it contains Playwright 1.8.0 which should 45 | resolve few issue that have open in the issue tracker. 46 | 47 | Full list of fixes and enhancements 48 | =================================== 49 | 50 | .. list-table:: 51 | :header-rows: 1 52 | 53 | * - ID 54 | - Type 55 | - Priority 56 | - Summary 57 | * - `#681`_ 58 | - enhancement 59 | - critical 60 | - Update to latest Playwright 61 | 62 | Altogether 1 issue. View on the `issue tracker `__. 63 | 64 | .. _#681: https://github.com/MarketSquare/robotframework-browser/issues/681 65 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-3.1.1.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 3.1.1 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 3.1.1 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v3.1.1 can be found 13 | from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==3.1.1 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 3.1.1 was released on Monday February 1, 2021. Browser supports 25 | Python **>=3.7**, and Robot Framework **>=3.2**. 26 | 27 | .. _Robot Framework: http://robotframework.org 28 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 29 | .. _Playwright: https://github.com/microsoft/playwright 30 | .. _pip: http://pip-installer.org 31 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 32 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av3.1.1 33 | 34 | 35 | .. contents:: 36 | :depth: 2 37 | :local: 38 | 39 | Full list of fixes and enhancements 40 | =================================== 41 | 42 | .. list-table:: 43 | :header-rows: 1 44 | 45 | * - ID 46 | - Type 47 | - Priority 48 | - Summary 49 | * - `#688`_ 50 | - bug 51 | - medium 52 | - Add Cookie keyword does not work if Epoch is not int like number 53 | * - `#674`_ 54 | - bug 55 | - --- 56 | - Unexpected Alerts will hang tests indefinitely 57 | 58 | Altogether 2 issues. View on the `issue tracker `__. 59 | 60 | .. _#688: https://github.com/MarketSquare/robotframework-browser/issues/688 61 | .. _#674: https://github.com/MarketSquare/robotframework-browser/issues/674 62 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-3.3.2.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 3.3.2 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 3.3.2 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v3.3.2 can be found 13 | from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==3.3.2 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 3.3.2 was released on Monday March 8, 2021. Browser supports 25 | Python **>=3.7**, and Robot Framework **>=3.2**. 26 | 27 | .. _Robot Framework: http://robotframework.org 28 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 29 | .. _Playwright: https://github.com/microsoft/playwright 30 | .. _pip: http://pip-installer.org 31 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 32 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av3.3.2 33 | 34 | 35 | .. contents:: 36 | :depth: 2 37 | :local: 38 | 39 | Full list of fixes and enhancements 40 | =================================== 41 | 42 | .. list-table:: 43 | :header-rows: 1 44 | 45 | * - ID 46 | - Type 47 | - Priority 48 | - Summary 49 | * - `#782`_ 50 | - bug 51 | - --- 52 | - Get Text not waiting anymore after upgrade from 3.2.0 to 3.3.1 53 | 54 | Altogether 1 issue. View on the `issue tracker `__. 55 | 56 | .. _#782: https://github.com/MarketSquare/robotframework-browser/issues/782 57 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-5.1.1.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 5.1.1 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 5.1.1 is a new release with 11 | a bug fix for "rfbrowser show-trace" command in Windows OS. 12 | All issues targeted for Browser library v5.1.1 can be found 13 | from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==5.1.1 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 5.1.1 was released on Saturday June 19, 2021. Browser supports 25 | Python 3.7+, and Robot Framework 3.2+. 26 | 27 | .. _Robot Framework: http://robotframework.org 28 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 29 | .. _Playwright: https://github.com/microsoft/playwright 30 | .. _pip: http://pip-installer.org 31 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 32 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av5.1.1 33 | 34 | 35 | .. contents:: 36 | :depth: 2 37 | :local: 38 | 39 | Most important enhancements 40 | =========================== 41 | 42 | rfbrowser show-trace can't find zipfile (`#1085`_) 43 | -------------------------------------------------- 44 | rfbrowser show-trace did not work in Windows OS, this is now fixed. 45 | 46 | Full list of fixes and enhancements 47 | =================================== 48 | 49 | .. list-table:: 50 | :header-rows: 1 51 | 52 | * - ID 53 | - Type 54 | - Priority 55 | - Summary 56 | * - `#1085`_ 57 | - bug 58 | - critical 59 | - rfbrowser show-trace can't find zipfile 60 | 61 | Altogether 1 issue. View on the `issue tracker `__. 62 | 63 | .. _#1085: https://github.com/MarketSquare/robotframework-browser/issues/1085 64 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-5.1.2.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 5.1.2 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 5.1.2 is a new release with 11 | bug fixe to Wait For Element State keyword custom error message. Also 12 | this release updates Playwright to 1.21.3. All issues targeted for 13 | Browser library v5.1.2 can be found from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==5.1.2 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 5.1.2 was released on Tuesday June 29, 2021. Browser supports 25 | Python 3.7+, Node 12/14 LTS and Robot Framework 3.2+. 26 | 27 | .. _Robot Framework: http://robotframework.org 28 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 29 | .. _Playwright: https://github.com/microsoft/playwright 30 | .. _pip: http://pip-installer.org 31 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 32 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av5.1.2 33 | 34 | 35 | .. contents:: 36 | :depth: 2 37 | :local: 38 | 39 | Full list of fixes and enhancements 40 | =================================== 41 | 42 | .. list-table:: 43 | :header-rows: 1 44 | 45 | * - ID 46 | - Type 47 | - Priority 48 | - Summary 49 | * - `#1110`_ 50 | - bug 51 | - medium 52 | - Message formatting in "Wait For Elements State" uses the given timeout even when it is None 53 | 54 | Altogether 1 issue. View on the `issue tracker `__. 55 | 56 | .. _#1110: https://github.com/MarketSquare/robotframework-browser/issues/1110 57 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-7.1.1.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 7.1.1 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 7.1.1 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v7.1.1 can be found 13 | from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==7.1.1 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 7.1.1 was released on Thursday August 26, 2021. Browser supports 25 | Python 3.7+, Node 12/14 LTS and Robot Framework 3.2+. Library was 26 | tested with Playwright 1.14.1 27 | 28 | .. _Robot Framework: http://robotframework.org 29 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 30 | .. _Playwright: https://github.com/microsoft/playwright 31 | .. _pip: http://pip-installer.org 32 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 33 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av7.1.1 34 | 35 | 36 | .. contents:: 37 | :depth: 2 38 | :local: 39 | 40 | Full list of fixes and enhancements 41 | =================================== 42 | 43 | .. list-table:: 44 | :header-rows: 1 45 | 46 | * - ID 47 | - Type 48 | - Priority 49 | - Summary 50 | * - `#1247`_ 51 | - --- 52 | - --- 53 | - Link keywords in example to docs 54 | * - `#1255`_ 55 | - --- 56 | - --- 57 | - Fix PLAYWRIGHT_BROWSERS_PATH overriding in entry.py 58 | 59 | Altogether 2 issues. View on the `issue tracker `__. 60 | 61 | .. _#1247: https://github.com/MarketSquare/robotframework-browser/issues/1247 62 | .. _#1255: https://github.com/MarketSquare/robotframework-browser/issues/1255 63 | -------------------------------------------------------------------------------- /docs/releasenotes/Browser-9.0.1.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Browser library 9.0.1 3 | ===================== 4 | 5 | 6 | .. default-role:: code 7 | 8 | 9 | Browser_ is a web testing library for `Robot Framework`_ that utilizes 10 | the Playwright_ tool internally. Browser library 9.0.1 is a new release with 11 | **UPDATE** enhancements and bug fixes. 12 | All issues targeted for Browser library v9.0.1 can be found 13 | from the `issue tracker`_. 14 | If you have pip_ installed, just run 15 | :: 16 | pip install --upgrade robotframework-browser 17 | rfbrowser init 18 | to install the latest available release or use 19 | :: 20 | pip install robotframework-browser==9.0.1 21 | rfbrowser init 22 | to install exactly this version. Alternatively you can download the source 23 | distribution from PyPI_ and install it manually. 24 | Browser library 9.0.1 was released on Thursday October 14, 2021. Browser supports 25 | Python 3.7+, Node 12/14 LTS and Robot Framework 3.2+. Library was 26 | tested with Playwright 1.15.2 27 | 28 | .. _Robot Framework: http://robotframework.org 29 | .. _Browser: https://github.com/MarketSquare/robotframework-browser 30 | .. _Playwright: https://github.com/microsoft/playwright 31 | .. _pip: http://pip-installer.org 32 | .. _PyPI: https://pypi.python.org/pypi/robotframework-browser 33 | .. _issue tracker: https://github.com/MarketSquare/robotframework-browser/milestones%3Av9.0.1 34 | 35 | 36 | .. contents:: 37 | :depth: 2 38 | :local: 39 | 40 | Most important enhancements 41 | =========================== 42 | FIXED issue: 43 | 44 | - After executing rfbrowser clean node, rfbrowser init is not possible anymore (`#1390`_) 45 | 46 | Full list of fixes and enhancements 47 | =================================== 48 | 49 | .. list-table:: 50 | :header-rows: 1 51 | 52 | * - ID 53 | - Type 54 | - Priority 55 | - Summary 56 | * - `#1390`_ 57 | - bug 58 | - critical 59 | - After executing rfbrowser clean node, rfbrowser init is not possible anymore 60 | 61 | Altogether 1 issue. View on the `issue tracker `__. 62 | 63 | .. _#1390: https://github.com/MarketSquare/robotframework-browser/issues/1390 64 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 40px; 3 | margin-right: auto; 4 | margin-left: auto; 5 | width: 700px; 6 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 7 | font-size: 14px; 8 | line-height: 20px; 9 | color: #333333; 10 | background-color: #ffffff; 11 | } 12 | 13 | a { 14 | color: #0088cc; 15 | text-decoration: none; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /node/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: "all", 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4 7 | }; -------------------------------------------------------------------------------- /node/build.testapp.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const esbuild = require('esbuild'); 4 | const { nodeExternalsPlugin } = require('esbuild-node-externals'); 5 | const fs = require('fs'); 6 | const path = require('path') 7 | 8 | const distPath = path.resolve(__dirname, './dynamic-test-app/dist') 9 | 10 | if (!fs.existsSync(distPath)) { 11 | fs.mkdirSync(distPath); 12 | } 13 | 14 | const indexHtmlSource = path.resolve(__dirname, './dynamic-test-app/static/index.html') 15 | const indexHtmlTarget = path.resolve(distPath, './index.html') 16 | fs.copyFileSync(indexHtmlSource, indexHtmlTarget) 17 | 18 | /* Build testApp frontend */ 19 | esbuild.build( 20 | { 21 | logLevel: "info", 22 | entryPoints: ['./node/dynamic-test-app/src/index.tsx'], 23 | bundle: true, 24 | platform: "browser", 25 | outfile: "./node/dynamic-test-app/dist/index.js", 26 | } 27 | ).catch(() => process.exit(1)); 28 | /* Build testApp backend */ 29 | esbuild.build( 30 | { 31 | logLevel: "info", 32 | entryPoints: ["./node/dynamic-test-app/src/server.ts"], 33 | bundle: true, 34 | platform: "node", 35 | outfile: "./node/dynamic-test-app/dist/server.js", 36 | /* plugins: [nodeExternalsPlugin()], */ 37 | } 38 | ).catch(() => process.exit(1)); 39 | -------------------------------------------------------------------------------- /node/build.wrapper.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const esbuild = require('esbuild'); 3 | const { nodeExternalsPlugin } = require('esbuild-node-externals'); 4 | 5 | esbuild.build( 6 | { 7 | logLevel: "info", 8 | entryPoints: ["./node/playwright-wrapper/index.ts"], 9 | bundle: true, 10 | platform: "node", 11 | outfile: "./Browser/wrapper/index.js", 12 | plugins: [nodeExternalsPlugin()], 13 | } 14 | ).catch(() => process.exit(1)); 15 | -------------------------------------------------------------------------------- /node/dynamic-test-app/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { HashRouter, Route, Routes } from 'react-router-dom'; 2 | import DragGame from './draggame'; 3 | import Login from './login'; 4 | import React from 'react'; 5 | 6 | export default function App() { 7 | return ( 8 | 9 | 10 | } /> 11 | } /> 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /node/dynamic-test-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './app'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/Moving Robot Framework browser automation to 2020 (or 2021).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarketSquare/robotframework-browser/e98197faab8e0f18491ab8eed938a472dc8e8bbe/node/dynamic-test-app/static/Moving Robot Framework browser automation to 2020 (or 2021).pdf -------------------------------------------------------------------------------- /node/dynamic-test-app/static/clock.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Clock Page 5 | 6 |
7 | 14 | 15 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/delayed-load.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | I have a delayed resource 7 | 8 | 9 |
10 | 13 |
14 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | color: black; 4 | background: #DDDDDD; 5 | } 6 | #container { 7 | width: 30em; 8 | height: 15em; 9 | margin: 5em auto; 10 | background: white; 11 | border: 1px solid gray; 12 | padding: 0.5em 2em; 13 | } 14 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/dialogs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | --- 7 |
8 | 9 |
10 |
11 |
12 | 13 | --- 14 |
15 | 16 |
17 |
18 |
19 | 20 | --- 21 |
22 | 23 |
24 |
25 | Let me out! 26 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/dogandcat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Promised to make an example 6 | 26 | 27 | 28 |
29 | 30 | Beginning 31 |
32 | 33 | 49 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error Page 5 | 6 | 7 | 8 |
9 |

Error Page

10 |

Login failed. Invalid user name and/or password.

11 | 12 | 13 |
14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

You're looking at bar.

4 | 5 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/deep/a.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

A HTML

4 | 5 | 6 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/deep/b.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | B HTML 4 | 5 | 6 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/deep/c.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is c 4 | 5 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/foo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

You're looking at foo.

4 | 5 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/frame_for_element_frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inside Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Header OneHeader TwoHeader Three
Data OneData TwoData Three
20 | 21 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/frame_for_elements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Home Page 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Header OneHeader TwoHeader Three
Data OneData TwoData Three
20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/frameset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/iframes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/left.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is LEFT side. Links:

4 |
    5 |
  • Open foo on the right-hand side frame 6 |
7 |

Form:

8 |
9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/poorlynamedframe.html: -------------------------------------------------------------------------------- 1 | 2 | Relative
3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/right.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

This is RIGHT side.

5 |

You're looking at right.

6 | 7 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/frames/search_results.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

You're looking at search results.

4 | 5 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/framing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | xxx iframes xxx 6 | 7 | 8 | 15 | 20 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/geolocation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | 31 | Geolocation test page 32 | 33 | 34 |

Click button to access geolocation.

35 | 36 |

37 | 38 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React App 7 | 8 | 9 | 10 |
11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/links/always_linked.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Always 6 | 7 | 8 | Always 9 | 10 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/links/link1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Link 1 6 | 7 | 8 | Link 2 9 | Always 10 | pdf 11 | 12 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/links/link2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Link 2 6 | 7 | 8 | Link 3 9 | Always 10 | 11 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/links/link3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Link 3 6 | 7 | 8 | Link 4 9 | Always 10 | 11 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/links/link4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Link 4 6 | 7 | 8 | Always 9 | 10 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/popup.html: -------------------------------------------------------------------------------- 1 | 2 |

Popped Up!

3 | 4 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/popups/call_popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Call Popups Page 6 | 7 | 8 |
Open First Popup
9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/popups/first_popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | First Popup 6 | 7 | 8 |
Open Second Popup
9 | 10 |
Close This popup
11 | 12 | 22 | 23 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/popups/second_popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Second Popup 6 | 7 | 8 | 9 | 10 |
Close This popup
11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/postredirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redirect on load with post 5 | 6 | 7 | 8 | 22 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/scrolling.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 29 | 30 | 31 |

Last pressed: NONE

32 |
33 |
34 |
Component 1
35 |
Component 2
36 |
Component 3
37 |
38 |
39 | 40 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/shell_game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | Shell Game 19 | 20 | 21 |
22 |

Choose the correct button!

23 |
24 | 25 | 26 | 27 | 28 | 29 |
30 |
<- THIS ONE
31 |
32 | 33 | 57 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/spaces.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page with spaces 6 | 7 | 8 |
  two spaces  
9 |
 many   spaces  
10 | 11 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/two_dialogs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Multiple alerts issue - example 6 | 7 | 8 |

9 | 10 |

11 |

12 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/webcomponent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web component 6 | 15 | 16 | 17 |
18 | 19 | 20 | 32 | -------------------------------------------------------------------------------- /node/dynamic-test-app/static/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome Page 5 | 6 | 7 | 8 |
9 |

Welcome Page

10 |

Login succeeded. Now you can logout.

11 | Open pdf 12 | Open html 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /node/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import sortImportsEs6Autofix from "eslint-plugin-sort-imports-es6-autofix"; 2 | import tsParser from "@typescript-eslint/parser"; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | import js from "@eslint/js"; 6 | import { FlatCompat } from "@eslint/eslintrc"; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | const compat = new FlatCompat({ 11 | baseDirectory: __dirname, 12 | recommendedConfig: js.configs.recommended, 13 | allConfig: js.configs.all 14 | }); 15 | 16 | export default [{ 17 | ignores: ["Browser/wrapper/generated/*"], 18 | }, ...compat.extends( 19 | "plugin:@typescript-eslint/recommended", 20 | "prettier", 21 | "plugin:prettier/recommended", 22 | ), { 23 | plugins: { 24 | "sort-imports-es6-autofix": sortImportsEs6Autofix, 25 | }, 26 | 27 | languageOptions: { 28 | parser: tsParser, 29 | ecmaVersion: 2020, 30 | sourceType: "module", 31 | 32 | parserOptions: { 33 | project: "./tsconfig.json", 34 | tsconfigRootDir: "./node", 35 | }, 36 | }, 37 | 38 | rules: { 39 | "@typescript-eslint/no-explicit-any": "off", 40 | 41 | "@typescript-eslint/ban-ts-comment": ["error", { 42 | "ts-expect-error": false, 43 | "ts-ignore": false, 44 | "ts-nocheck": false, 45 | "ts-check": false, 46 | minimumDescriptionLength: 5, 47 | }], 48 | 49 | "sort-imports-es6-autofix/sort-imports-es6": ["error", {}], 50 | }, 51 | }]; -------------------------------------------------------------------------------- /node/playwright-wrapper/cookie.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020- Robot Framework Foundation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { BrowserContext, Cookie } from 'playwright'; 16 | 17 | import { Request, Response } from './generated/playwright_pb'; 18 | import { emptyWithLog, jsonResponse } from './response-util'; 19 | 20 | import { pino } from 'pino'; 21 | const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime }); 22 | 23 | interface CookieData { 24 | name: string; 25 | value: string; 26 | url?: string; 27 | domain?: string; 28 | path?: string; 29 | expires?: number; 30 | httpOnly?: boolean; 31 | secure?: boolean; 32 | sameSite?: 'Strict' | 'Lax' | 'None'; 33 | } 34 | 35 | export async function getCookies(context: BrowserContext): Promise { 36 | const allCookies = await context.cookies(); 37 | logger.info({ 'Cookies: ': allCookies }); 38 | const cookieName = []; 39 | for (const cookie of allCookies as Array) { 40 | cookieName.push(cookie.name); 41 | } 42 | return jsonResponse(JSON.stringify(allCookies), cookieName.toString()); 43 | } 44 | 45 | export async function addCookie(request: Request.Json, context: BrowserContext): Promise { 46 | const cookie: CookieData = JSON.parse(request.getBody()); 47 | logger.info({ 'Cookie data: ': request.getBody() }); 48 | await context.addCookies([cookie]); 49 | return emptyWithLog('Cookie "' + cookie.name + '" added.'); 50 | } 51 | 52 | export async function deleteAllCookies(context: BrowserContext): Promise { 53 | await context.clearCookies(); 54 | return emptyWithLog('All cookies deleted.'); 55 | } 56 | -------------------------------------------------------------------------------- /node/playwright-wrapper/device-descriptors.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020- Robot Framework Foundation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { devices } from 'playwright'; 16 | 17 | import { Request, Response } from './generated/playwright_pb'; 18 | import { jsonResponse } from './response-util'; 19 | 20 | export function getDevice(request: Request.Device): Response.Json { 21 | const name = request.getName(); 22 | const device = devices[name]; 23 | if (!device) throw new Error(`No device named ${name}`); 24 | return jsonResponse(JSON.stringify(device), ''); 25 | } 26 | 27 | export function getDevices(): Response.Json { 28 | return jsonResponse(JSON.stringify(devices), ''); 29 | } 30 | -------------------------------------------------------------------------------- /node/playwright-wrapper/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020- Robot Framework Foundation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { PlaywrightServer } from './grpc-service'; 16 | import { Server, ServerCredentials, ServiceDefinition, UntypedServiceImplementation } from '@grpc/grpc-js'; 17 | 18 | import { PlaywrightService } from './generated/playwright_grpc_pb'; 19 | import { pino } from 'pino'; 20 | const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime }); 21 | 22 | const args = process.argv.slice(2); 23 | 24 | const host = args[0]; 25 | const port = args[1]; 26 | 27 | if (!host) { 28 | throw new Error(`No host defined`); 29 | } 30 | 31 | if (!port) { 32 | throw new Error(`No port defined`); 33 | } 34 | 35 | const server = new Server(); 36 | server.addService( 37 | PlaywrightService as unknown as ServiceDefinition, 38 | new PlaywrightServer() as unknown as UntypedServiceImplementation, 39 | ); 40 | 41 | server.bindAsync(`${host}:${port}`, ServerCredentials.createInsecure(), () => { 42 | logger.info(`Listening on ${host}:${port}`); 43 | server.start(); 44 | }); 45 | -------------------------------------------------------------------------------- /node/playwright-wrapper/keyword-decorators.ts: -------------------------------------------------------------------------------- 1 | import { pino } from 'pino'; 2 | const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime }); 3 | 4 | // Idea and async logger method from https://github.com/norbornen/execution-time-decorator/ 5 | // eslint-disable-next-line 6 | export function class_async_logger(target: Function) { 7 | for (const propertyName of Object.keys(target.prototype)) { 8 | const descriptor = Object.getOwnPropertyDescriptor(target.prototype, propertyName); 9 | const isMethod = descriptor?.value instanceof Function; 10 | if (!isMethod || !descriptor) continue; 11 | 12 | const timered_method = async_logger(propertyName, descriptor); 13 | Object.defineProperty(target.prototype, propertyName, timered_method); 14 | } 15 | } 16 | 17 | export function async_logger(propertyKey: string, propertyDescriptor: PropertyDescriptor): PropertyDescriptor { 18 | const originalMethod = propertyDescriptor.value; 19 | propertyDescriptor.value = async function (...args: any[]) { 20 | try { 21 | logger.info(`Start of node method ${propertyKey}`); 22 | const result = await originalMethod.apply(this, args); 23 | logger.info(`End of node method ${propertyKey}`); 24 | return result; 25 | } catch (err) { 26 | logger.info(`Error of node method ${propertyKey}`); 27 | throw err; 28 | } 29 | }; 30 | return propertyDescriptor; 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "robotframework-playwright", 3 | "version": "19.5.1", 4 | "main": "index.ts", 5 | "author": "Mikko Korpela ", 6 | "license": "Apache-2.0", 7 | "devDependencies": { 8 | "@types/express": "^5.0.2", 9 | "@types/react": "^18.3.12", 10 | "@types/react-dom": "^18.3.1", 11 | "@types/react-router-dom": "^5.3.3", 12 | "@types/strip-comments": "^2.0.4", 13 | "@types/uuid": "^10.0.0", 14 | "@typescript-eslint/eslint-plugin": "^8.33.1", 15 | "@typescript-eslint/parser": "^8.32.1", 16 | "esbuild": "^0.25.5", 17 | "esbuild-node-externals": "^1.18.0", 18 | "eslint": "^9.28.0", 19 | "eslint-config-prettier": "^10.1.5", 20 | "eslint-plugin-prettier": "^5.4.1", 21 | "eslint-plugin-sort-imports-es6-autofix": "^0.6.0", 22 | "express": "^5.1.0", 23 | "grpc_tools_node_protoc_ts": "^5.3.3", 24 | "grpc-tools": "^1.13.0", 25 | "husky": "^9.1.7", 26 | "lint-staged": "^16.1.0", 27 | "prettier": "^3.5.3", 28 | "react": "^18.3.0", 29 | "react-dnd": "^16.0.1", 30 | "react-dnd-html5-backend": "^16.0.1", 31 | "react-dom": "^18.3.1", 32 | "react-router-dom": "^7.6.2", 33 | "ts-loader": "^9.5.2", 34 | "ts-node": "^10.9.2", 35 | "typescript": "^5.8.3" 36 | }, 37 | "dependencies": { 38 | "@grpc/grpc-js": "^1.13.4", 39 | "@medv/finder": "^4.0.2", 40 | "google-protobuf": "3.21.4", 41 | "monocart-coverage-reports": "^2.12.6", 42 | "pino": "^9.7.0", 43 | "playwright": "^1.52.0", 44 | "strip-comments": "^2.0.1", 45 | "uuid": "^11.1.0" 46 | }, 47 | "scripts": { 48 | "build-test-app": "node ./node/build.testapp.js", 49 | "build": "node ./node/build.wrapper.js", 50 | "lint": "eslint \"node/**/*.{ts,tsx}\" --quiet --fix --config ./node/eslint.config.mjs", 51 | "grpc_tools_node_protoc": "grpc_tools_node_protoc" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from setuptools import setup, find_packages # type: ignore 4 | import sys 5 | 6 | sys.path.append("Browser") 7 | 8 | with open("README.md", encoding="utf-8") as f: 9 | long_description = f.read() 10 | 11 | packages = find_packages(exclude=["utest", "atest"]) 12 | 13 | package_data = { 14 | "": ["*"], 15 | "Browser": [ 16 | "wrapper/index.js", 17 | "wrapper/package.json", 18 | "wrapper/package-lock.json", 19 | "wrapper/static/selector-finder.js", 20 | ], 21 | } 22 | 23 | install_requires = open(os.path.join("Browser", "requirements.txt")).readlines() 24 | 25 | setup_kwargs = { 26 | "name": "robotframework-browser", 27 | "version": "19.5.1", 28 | "description": "Robot Framework Browser library powered by Playwright. Aiming for speed, reliability and visibility.", 29 | "long_description": long_description, 30 | "long_description_content_type": "text/markdown", 31 | "author": "MarketSquare - Robot Framework community", 32 | "author_email": "mikko.korpela@gmail.com", 33 | "maintainer": None, 34 | "maintainer_email": None, 35 | "url": "https://github.com/MarketSquare/robotframework-browser", 36 | "packages": packages, 37 | "package_dir": {"": "."}, 38 | "package_data": package_data, 39 | "include_package_data": True, 40 | "install_requires": install_requires, 41 | "extras_require": { 42 | "tidy": ["robotframework-tidy>=4.12.0"] 43 | }, 44 | "entry_points": {"console_scripts": ["rfbrowser=Browser.entry.__main__:cli"]}, 45 | "python_requires": ">=3.9,<4.0", 46 | "classifiers": [ 47 | "Development Status :: 5 - Production/Stable", 48 | "License :: OSI Approved :: Apache Software License", 49 | "Operating System :: OS Independent", 50 | "Programming Language :: Python :: 3", 51 | "Topic :: Software Development :: Testing", 52 | "Framework :: Robot Framework", 53 | "Framework :: Robot Framework :: Library", 54 | ], 55 | "include_package_data": True, 56 | } 57 | 58 | 59 | setup(**setup_kwargs) 60 | -------------------------------------------------------------------------------- /utest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarketSquare/robotframework-browser/e98197faab8e0f18491ab8eed938a472dc8e8bbe/utest/__init__.py -------------------------------------------------------------------------------- /utest/approvaltests_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "subdirectory": "approved_files" 3 | } 4 | -------------------------------------------------------------------------------- /utest/approved_files/test_context_cache.test_add_cache.approved.txt: -------------------------------------------------------------------------------- 1 | { 2 | "a1": { 3 | "video": "/path/to/file", 4 | "size": { 5 | "x": 1, 6 | "y": 2 7 | } 8 | }, 9 | "b2": { 10 | "video": "/path/to/file", 11 | "size": { 12 | "x": 1, 13 | "y": 2 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utest/approved_files/test_context_cache.test_cache_is_empty.approved.txt: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /utest/approved_files/test_context_cache.test_get_item.approved.txt: -------------------------------------------------------------------------------- 1 | { 2 | "video": "/path/to/file", 3 | "size": { 4 | "x": 1, 5 | "y": 2 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /utest/approved_files/test_context_cache.test_get_item_no_item.approved.txt: -------------------------------------------------------------------------------- 1 | null 2 | -------------------------------------------------------------------------------- /utest/approved_files/test_context_cache.test_remove_item.approved.txt: -------------------------------------------------------------------------------- 1 | { 2 | "b2": { 3 | "video": "/path/to/file", 4 | "size": { 5 | "x": 1, 6 | "y": 2 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /utest/approved_files/test_context_cache.test_remove_item_no_item.approved.txt: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /utest/approved_files/test_get_video_size.test_get_video_size.approved.txt: -------------------------------------------------------------------------------- 1 | Video size 2 | 3 | 0) {'width': 1280, 'height': 720} 4 | 1) {'width': 1280, 'height': 720} 5 | 2) {'width': 12, 'height': 11} 6 | 3) {'width': 123, 'height': 111} 7 | 4) {'width': 12, 'height': 11} 8 | -------------------------------------------------------------------------------- /utest/approved_files/test_presenter_mode_conversion.test_presenter_mode_default.approved.txt: -------------------------------------------------------------------------------- 1 | {'duration': datetime.timedelta(seconds=2), 'width': '2px', 'style': 'dotted', 'color': 'blue'} 2 | -------------------------------------------------------------------------------- /utest/approved_files/test_presenter_mode_conversion.test_presenter_mode_duration_as_string.approved.txt: -------------------------------------------------------------------------------- 1 | {'duration': datetime.timedelta(seconds=4), 'width': '2px', 'style': 'dotted', 'color': 'white'} 2 | -------------------------------------------------------------------------------- /utest/approved_files/test_presenter_mode_conversion.test_presenter_mode_duration_as_timedelta.approved.txt: -------------------------------------------------------------------------------- 1 | {'duration': datetime.timedelta(seconds=5), 'width': '2px', 'style': 'dotted', 'color': 'black'} 2 | -------------------------------------------------------------------------------- /utest/approved_files/test_rfbrowser_translate.test_body_line.approved.txt: -------------------------------------------------------------------------------- 1 | body 2 | 3 | 0) | new_page | Keyword is missing translation | 4 | 1) | this_is_long_keyword_which_is_42_chars_len | Keyword is missing translation | 5 | 2) | close | Documentation update needed | 6 | 3) | new_page | Keyword is missing translation | 7 | 4) | click | Keyword not found from library | 8 | 5) | click | Documentation update needed | 9 | -------------------------------------------------------------------------------- /utest/approved_files/test_rfbrowser_translate.test_full_long_kw_table.approved.txt: -------------------------------------------------------------------------------- 1 | all with long kw name 2 | 3 | 0) | Keyword name | Reason | 4 | 1) | ------------------------------------------ | --------------------------------------- | 5 | 2) | new_page | Keyword translation is missing checksum | 6 | 3) | this_is_long_keyword_which_is_42_chars_len | Keyword is missing translation | 7 | 4) | close | Documentation update needed | 8 | -------------------------------------------------------------------------------- /utest/approved_files/test_rfbrowser_translate.test_heading.approved.txt: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 0) | Keyword name | Reason | 4 | 1) | ------------------------------------------ | --------------------------------------- | 5 | 2) | Keyword name | Reason | 6 | 3) | ------------ | --------------------------------------- | 7 | -------------------------------------------------------------------------------- /utest/approved_files/test_type_converter.test_type_converter.approved.txt: -------------------------------------------------------------------------------- 1 | Type converter 2 | 3 | 0) nonetype 4 | 1) str 5 | 2) int 6 | 3) bool 7 | -------------------------------------------------------------------------------- /utest/conftest.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def response(): 8 | response = MagicMock() 9 | response.log = "" 10 | response.body = "" 11 | return response 12 | 13 | 14 | @pytest.fixture 15 | def ctx(response): 16 | ctx = MagicMock() 17 | pw = MagicMock() 18 | grpc = MagicMock() 19 | get_text = MagicMock() 20 | get_text.GetText = MagicMock(return_value=response) 21 | enter = MagicMock(return_value=get_text) 22 | grpc.__enter__ = enter 23 | pw.grpc_channel.return_value = grpc 24 | ctx.playwright = pw 25 | return ctx 26 | -------------------------------------------------------------------------------- /utest/custom_locator_handler.js: -------------------------------------------------------------------------------- 1 | async function customLocatorHandler(locator, clickLocator, page) { 2 | console.log("Adding custom locator handler for: " + locator); 3 | const pageLocator = page.locator(locator).first(); 4 | await page.addLocatorHandler( 5 | pageLocator, 6 | async () => { 7 | console.log("Handling custom locator: " + clickLocator); 8 | await page.locator(clickLocator).click(); 9 | // Return the element that was clicked 10 | } 11 | ); 12 | } 13 | exports.__esModule = true; 14 | exports.customLocatorHandler = customLocatorHandler; 15 | -------------------------------------------------------------------------------- /utest/robotframework_browser_translation_as_list/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def get_language() -> list: 5 | curr_dir = Path(__file__).parent.absolute() 6 | return [ 7 | {"language": "eng", "path": curr_dir / "translate_1.json"}, 8 | {"language": "swe", "path": curr_dir / "translate_2.json"}, 9 | ] 10 | -------------------------------------------------------------------------------- /utest/robotframework_browser_translation_fi/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def get_language() -> dict: 5 | curr_dir = Path(__file__).parent.absolute() 6 | return {"language": "fi", "path": curr_dir / "translate.json"} 7 | -------------------------------------------------------------------------------- /utest/robotframework_browser_translation_not_working/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def get_language_not_working() -> dict: 5 | curr_dir = Path(__file__).parent.absolute() 6 | return {"language": "fi", "path": curr_dir / "translate.json"} 7 | -------------------------------------------------------------------------------- /utest/test_browser_folder_cleanup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import tempfile 3 | from pathlib import Path 4 | from unittest.mock import patch, PropertyMock 5 | 6 | import pytest 7 | 8 | import Browser 9 | 10 | 11 | @pytest.fixture() 12 | def browser(): 13 | return Browser.Browser() 14 | 15 | 16 | @pytest.mark.skipif(sys.platform == "win32", reason="Cleanup does not work in Windows") 17 | def test_cleanup_browser_folder_no_folder(browser): 18 | Browser._suite_cleanup_done = False 19 | with tempfile.TemporaryDirectory() as tmp_dir: 20 | browser_folder = Path(tmp_dir) / "browser" 21 | assert not browser_folder.is_dir() 22 | browser_folder.mkdir() 23 | screenshot = browser_folder / "screenshot" 24 | screenshot.mkdir() 25 | video = browser_folder / "video" 26 | video.mkdir() 27 | traces = browser_folder / "traces" 28 | traces.mkdir() 29 | state = browser_folder / "state" 30 | state.mkdir() 31 | foobar = browser_folder / "foobar" 32 | foobar.mkdir() 33 | with patch( 34 | "Browser.Browser.outputdir", new_callable=PropertyMock 35 | ) as mock_property: 36 | mock_property.return_value = tmp_dir 37 | browser._start_suite(None, {"id": "s1"}) 38 | assert browser_folder.is_dir() 39 | assert not screenshot.is_dir() 40 | assert not video.is_dir() 41 | assert not traces.is_dir() 42 | assert not state.is_dir() 43 | assert foobar.is_dir() 44 | 45 | 46 | @pytest.mark.skipif(sys.platform == "win32", reason="Cleanup does not work in Windows") 47 | def test_cleanup_browser_folder_folder(browser): 48 | with tempfile.TemporaryDirectory() as tmp_dir: 49 | browser_folder = Path(tmp_dir) / "browser" 50 | assert not browser_folder.is_dir() 51 | with patch( 52 | "Browser.Browser.outputdir", new_callable=PropertyMock 53 | ) as mock_property: 54 | mock_property.return_value = tmp_dir 55 | browser._start_suite(None, {"id": "s1"}) 56 | assert not browser_folder.is_dir() 57 | browser_folder.mkdir() 58 | browser._start_suite(None, {"id": "s1"}) 59 | assert browser_folder.is_dir() 60 | -------------------------------------------------------------------------------- /utest/test_context_cache.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | from approvaltests.approvals import verify # type: ignore 5 | 6 | from Browser.base import ContextCache 7 | 8 | ITEM = {"video": "/path/to/file", "size": {"x": 1, "y": 2}} 9 | 10 | 11 | @pytest.fixture 12 | def cache(): 13 | return ContextCache() 14 | 15 | 16 | @pytest.fixture 17 | def cache_items(cache: ContextCache): 18 | cache.add("a1", ITEM) 19 | cache.add("b2", ITEM) 20 | return cache 21 | 22 | 23 | def _verify(athing): 24 | verify(json.dumps(athing, indent=4) + "\n") 25 | 26 | 27 | def _verify_cache(cache: ContextCache): 28 | _verify(cache.cache) 29 | 30 | 31 | def test_cache_is_empty(cache: ContextCache): 32 | _verify_cache(cache) 33 | 34 | 35 | def test_add_cache(cache: ContextCache): 36 | cache.add("a1", ITEM) 37 | cache.add("b2", ITEM) 38 | _verify_cache(cache) 39 | 40 | 41 | def test_remove_item_no_item(cache: ContextCache): 42 | cache.remove("a1") 43 | _verify_cache(cache) 44 | 45 | 46 | def test_remove_item(cache_items: ContextCache): 47 | cache_items.remove("a1") 48 | _verify_cache(cache_items) 49 | 50 | 51 | def test_get_item(cache_items: ContextCache): 52 | _verify(cache_items.get("a1")) 53 | 54 | 55 | def test_get_item_no_item(cache_items: ContextCache): 56 | _verify(cache_items.get("not-here")) 57 | -------------------------------------------------------------------------------- /utest/test_cookie.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone 2 | 3 | import pytest 4 | 5 | from Browser.keywords import Cookie 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def cookie(): 10 | return Cookie(None) 11 | 12 | 13 | def test_cookie_as_dot_dict_expiry(cookie: Cookie): 14 | epoch = 1604698517 15 | data = cookie._cookie_as_dot_dict({"expires": epoch}) 16 | assert data.expires == datetime.fromtimestamp(epoch, tz=timezone.utc) 17 | 18 | 19 | def test_cookie_as_dot_dict_negative_expiry(cookie: Cookie): 20 | epoch = -1 21 | data = cookie._cookie_as_dot_dict({"expires": epoch}) 22 | assert data.expires == datetime.fromtimestamp(epoch, tz=timezone.utc) 23 | 24 | 25 | def test_expiry(cookie: Cookie): 26 | assert cookie._expiry("1") == 1 27 | assert cookie._expiry("1 000") == 1000 28 | assert cookie._expiry("10,0") == 10 29 | assert cookie._expiry("10 000.0") == 10000 30 | -------------------------------------------------------------------------------- /utest/test_deprecated.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from Browser.utils.deprecated import _is_deprecated_attribute, _method_to_keyword 4 | 5 | 6 | class DummyClass: 7 | def keyword_with_args(self, arg1, arg2): 8 | pass 9 | 10 | def keyword_with(self, arg1, *args, **kwargs): 11 | pass 12 | 13 | 14 | @pytest.fixture 15 | def keyword(): 16 | return DummyClass().keyword_with_args 17 | 18 | 19 | def test_method_to_keyword(): 20 | assert _method_to_keyword("bar") == "Bar" 21 | assert _method_to_keyword("BAR") == "Bar" 22 | assert _method_to_keyword("BaR_FoO") == "Bar Foo" 23 | 24 | 25 | def test_is_deprecated_no_deprecate(keyword): 26 | assert _is_deprecated_attribute(keyword, False, (DummyClass(),), ()) is False 27 | 28 | 29 | def test_is_deprecated_no_deprecate_usage(keyword): 30 | assert _is_deprecated_attribute(keyword, "arg2", (DummyClass(),), ()) is False 31 | assert _is_deprecated_attribute(keyword, "arg2", (DummyClass(), True), ()) is False 32 | 33 | 34 | def test_test_is_deprecated_kwargs(keyword): 35 | assert ( 36 | _is_deprecated_attribute(keyword, "kw_arg2", (DummyClass(),), ("kw_arg2",)) 37 | is True 38 | ) 39 | assert _is_deprecated_attribute(keyword, "kw_arg2", (DummyClass(),), ()) is False 40 | assert ( 41 | _is_deprecated_attribute(keyword, "kw_arg2", (DummyClass(),), ("kw_arg1",)) 42 | is False 43 | ) 44 | 45 | 46 | def test_test_is_deprecated_args(keyword): 47 | assert _is_deprecated_attribute(keyword, "arg1", (DummyClass(), True), ()) is True 48 | assert ( 49 | _is_deprecated_attribute(keyword, "arg2", (DummyClass(), True, True), ()) 50 | is True 51 | ) 52 | -------------------------------------------------------------------------------- /utest/test_development_functionality.py: -------------------------------------------------------------------------------- 1 | from Browser.keywords.playwright_state import PlaywrightState 2 | 3 | 4 | def test_pause_on_failure(): 5 | def whole_lib(): 6 | pass 7 | 8 | whole_lib.pause_on_failure = set() 9 | whole_lib.playwright = whole_lib 10 | browser = PlaywrightState(whole_lib) 11 | 12 | def func(*args, **kwargs): 13 | pass 14 | 15 | browser.new_browser = func 16 | browser.new_context = func 17 | browser.new_page = func 18 | browser.open_browser() 19 | assert whole_lib.pause_on_failure 20 | -------------------------------------------------------------------------------- /utest/test_docs.py: -------------------------------------------------------------------------------- 1 | import Browser 2 | 3 | 4 | def test_all_keywords_have_docs(): 5 | browser = Browser.Browser() 6 | for name in browser.get_keyword_names(): 7 | assert len(browser.get_keyword_documentation(name)) > 1, ( 8 | f"Keyword '{name}' is missing docs" 9 | ) 10 | -------------------------------------------------------------------------------- /utest/test_format_cookie.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import datetime, timezone 3 | 4 | import pytest 5 | 6 | from Browser.keywords import Cookie 7 | 8 | 9 | @pytest.fixture 10 | def cookie(): 11 | return Cookie(None) 12 | 13 | 14 | def test_one_cookie_as_string(cookie: Cookie): 15 | as_string = cookie._format_cookies_as_string( 16 | [{"name": "tidii", "value": 1111, "expires": -1}] 17 | ) 18 | assert as_string == "tidii=1111" 19 | 20 | 21 | def test_many_cookies_as_string(cookie: Cookie): 22 | cookies = [ 23 | {"name": "tidii", "value": 1111, "httpOnly": False}, 24 | {"name": "foo", "value": "bar", "httpOnly": True}, 25 | ] 26 | as_string = cookie._format_cookies_as_string(cookies) 27 | assert as_string == "tidii=1111; foo=bar" 28 | 29 | 30 | def test_as_dot_dict(cookie: Cookie): 31 | dot_dict = cookie._format_cookies_as_dot_dict( 32 | [{"name": "tidii", "value": 1111, "expires": -1}] 33 | ) 34 | assert dot_dict[0].name == "tidii" 35 | assert dot_dict[0].value == 1111 36 | assert dot_dict[0].expires == datetime.fromtimestamp(-1, tz=timezone.utc) 37 | -------------------------------------------------------------------------------- /utest/test_get_normalized_keyword.py: -------------------------------------------------------------------------------- 1 | from Browser.utils import get_normalized_keyword 2 | 3 | 4 | def test_get_normalized_keyword(): 5 | assert get_normalized_keyword("Tidii") == "tidii" 6 | assert get_normalized_keyword("TiDii") == "ti_dii" 7 | assert get_normalized_keyword("Tidii SomeThing") == "tidii_something" 8 | assert get_normalized_keyword("ThisIsKeyword") == "this_is_keyword" 9 | -------------------------------------------------------------------------------- /utest/test_get_text.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from Browser.keywords import Getters 4 | 5 | 6 | class Response: 7 | log = "Log text" 8 | body = "element text" 9 | 10 | 11 | def test_get_text(ctx: MagicMock): 12 | getter = Getters(ctx) 13 | getter._get_text = MagicMock(return_value=Response()) # type: ignore[assignment] 14 | text = getter.get_text("//div") 15 | assert text == "element text" 16 | -------------------------------------------------------------------------------- /utest/test_get_time.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | import pytest 4 | 5 | from Browser import Browser 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def lib(): 10 | return Browser() 11 | 12 | 13 | def test_none(lib: Browser): 14 | assert lib.get_timeout(None) == 10000 15 | assert Browser(timeout=timedelta(seconds=0.001)).timeout == 1 16 | 17 | 18 | def test_timedelta(lib: Browser): 19 | assert lib.get_timeout(timedelta(seconds=1)) == 1000 20 | assert lib.get_timeout(timedelta(seconds=0)) == 0 21 | assert lib.get_timeout(timedelta(milliseconds=10)) == 10 22 | 23 | 24 | def test_rf_time(lib: Browser): 25 | assert lib.get_timeout("2 s") == 2000 # type: ignore 26 | 27 | 28 | def test_convert_timeout(lib: Browser): 29 | assert lib.convert_timeout(0.1) == 100 30 | assert lib.convert_timeout(0.1, False) == 0.1 31 | 32 | 33 | def test_millisecs_to_timestr(lib: Browser): 34 | assert lib.millisecs_to_timestr(1000) == "1 second" 35 | assert lib.millisecs_to_timestr(1) == "1 millisecond" 36 | assert lib.millisecs_to_timestr(3600000) == "1 hour" 37 | -------------------------------------------------------------------------------- /utest/test_get_video_size.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from approvaltests.approvals import verify_all # type: ignore 3 | 4 | from Browser.keywords import PlaywrightState 5 | 6 | 7 | @pytest.fixture 8 | def state(): 9 | return PlaywrightState(None) 10 | 11 | 12 | def test_get_video_size(state: PlaywrightState): 13 | results = [ 14 | state._get_video_size({}), 15 | state._get_video_size({"recordVideo": {"path": "/path/to"}}), 16 | state._get_video_size({"recordVideo": {"size": {"width": 12, "height": 11}}}), 17 | state._get_video_size({"viewport": {"width": 123, "height": 111}}), 18 | state._get_video_size( 19 | { 20 | "recordVideo": {"size": {"width": 12, "height": 11}}, 21 | "viewport": {"width": 123, "height": 111}, 22 | } 23 | ), 24 | ] 25 | verify_all("Video size", results) 26 | -------------------------------------------------------------------------------- /utest/test_output_dir.py: -------------------------------------------------------------------------------- 1 | import Browser 2 | 3 | 4 | def test_output_dir(): 5 | browser = Browser.Browser() 6 | assert browser.outputdir == "." 7 | browser.outputdir = "/foo/bar" 8 | assert browser.outputdir == "/foo/bar" 9 | -------------------------------------------------------------------------------- /utest/test_presenter_mode_conversion.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | from unittest.mock import MagicMock, PropertyMock 3 | 4 | from approvaltests.approvals import verify # type: ignore 5 | 6 | from Browser.base.librarycomponent import LibraryComponent 7 | 8 | 9 | def test_presenter_mode_default(): 10 | lib = MagicMock() 11 | type(lib).presenter_mode = {} 12 | ctx = LibraryComponent(lib) 13 | verify(ctx.get_presenter_mode) 14 | 15 | 16 | def test_presenter_mode_duration_as_string(): 17 | lib = MagicMock() 18 | p = PropertyMock(return_value={"color": "white", "duration": "4s"}) 19 | type(lib).presenter_mode = p 20 | ctx = LibraryComponent(lib) 21 | verify(ctx.get_presenter_mode) 22 | 23 | 24 | def test_presenter_mode_duration_as_timedelta(): 25 | lib = MagicMock() 26 | duration = timedelta(seconds=5) 27 | p = PropertyMock(return_value={"color": "black", "duration": duration}) 28 | type(lib).presenter_mode = p 29 | ctx = LibraryComponent(lib) 30 | verify(ctx.get_presenter_mode) 31 | -------------------------------------------------------------------------------- /utest/test_rfbrowser_translate.py: -------------------------------------------------------------------------------- 1 | from approvaltests import verify_all 2 | 3 | from Browser.entry.translation import ( 4 | MISSING_CHECKSUM, 5 | _get_heading, 6 | _table_doc_updated, 7 | DOC_CHANGED, 8 | NO_LIB_KEYWORD, 9 | MISSING_TRANSLATION, 10 | ) 11 | 12 | 13 | def test_heading(): 14 | verify_all("header", [*_get_heading(42), *_get_heading(6)]) 15 | 16 | 17 | def test_body_line(): 18 | verify_all( 19 | "body", 20 | [ 21 | _table_doc_updated("new_page", 42, MISSING_TRANSLATION), 22 | _table_doc_updated( 23 | "this_is_long_keyword_which_is_42_chars_len", 42, MISSING_TRANSLATION 24 | ), 25 | _table_doc_updated("close", 42, DOC_CHANGED), 26 | _table_doc_updated("new_page", 8, MISSING_TRANSLATION), 27 | _table_doc_updated("click", 8, NO_LIB_KEYWORD), 28 | _table_doc_updated("click", 8, DOC_CHANGED), 29 | ], 30 | ) 31 | 32 | 33 | def test_full_long_kw_table(): 34 | lines = _get_heading(42) 35 | lines.append(_table_doc_updated("new_page", 42, MISSING_CHECKSUM)) 36 | lines.append( 37 | _table_doc_updated( 38 | "this_is_long_keyword_which_is_42_chars_len", 42, MISSING_TRANSLATION 39 | ) 40 | ) 41 | lines.append(_table_doc_updated("close", 42, DOC_CHANGED)) 42 | verify_all("all with long kw name", lines) 43 | -------------------------------------------------------------------------------- /utest/test_screenshot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | from typing import Generator 4 | import pytest 5 | import Browser 6 | import uuid 7 | 8 | 9 | @pytest.fixture() 10 | def application_server(): 11 | process = subprocess.Popen( 12 | ["node", "./node/dynamic-test-app/dist/server.js", "-p", "7272"] 13 | ) 14 | yield 15 | process.terminate() 16 | 17 | 18 | @pytest.fixture() 19 | def browser(tmpdir): 20 | Browser.Browser._output_dir = tmpdir 21 | browser = Browser.Browser(highlight_on_failure=True) 22 | yield browser 23 | browser.close_browser("ALL") 24 | 25 | 26 | def test_take_screenshot(application_server, browser): 27 | browser.new_page("localhost:7272/dist/") 28 | screenshot_path = browser.take_screenshot(r"screenshot-{}".format(uuid.uuid4())) 29 | assert os.path.exists(screenshot_path) 30 | screenshot_path = browser.take_screenshot() 31 | assert os.path.exists(screenshot_path) 32 | -------------------------------------------------------------------------------- /utest/test_type_converter.py: -------------------------------------------------------------------------------- 1 | from approvaltests.approvals import verify_all # type: ignore 2 | 3 | from Browser.utils.misc import type_converter 4 | 5 | 6 | def test_type_converter(): 7 | results = [ 8 | type_converter(None), 9 | type_converter(""), 10 | type_converter(1), 11 | type_converter(True), 12 | ] 13 | verify_all("Type converter", results) 14 | -------------------------------------------------------------------------------- /utest/test_waiters.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from Browser import ElementState 4 | from Browser.keywords import Waiter 5 | 6 | 7 | class Response: 8 | log = "log message" 9 | 10 | 11 | def test_wait_for_state(ctx: MagicMock, response: MagicMock): 12 | wait = Waiter(ctx) 13 | wait._wait_for_elements_state = MagicMock(return_value=Response()) # type: ignore 14 | wait.wait_for_elements_state("id=myText") 15 | 16 | 17 | def test_wait_for_state_error(): 18 | ctx = MagicMock() 19 | ctx.timeout = 1000 20 | wait = Waiter(ctx) 21 | wait._wait_for_elements_state = MagicMock(side_effect=[AssertionError, "one"]) 22 | wait.wait_for_elements_state("id=myText") 23 | 24 | 25 | def test_wait_for_function(): 26 | ctx = MagicMock() 27 | ctx.timeout = 1000 28 | wait = Waiter(ctx) 29 | wait._wait_for_function = MagicMock(side_effect=[AssertionError, "10"]) 30 | wait.wait_for_elements_state("id=myText", ElementState.checked) 31 | --------------------------------------------------------------------------------