Merge pull request #994 from seleniumbase/recorder-mode-algorithms

Updates to Recorder Mode and the Page Objects generator
This commit is contained in:
Michael Mintz 2021-10-02 00:13:17 -04:00 committed by GitHub
commit e27163faac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 337 additions and 14 deletions

View File

@ -5,8 +5,8 @@
<meta property="og:image" content="https://seleniumbase.io/cdn/img/mac_sb_logo_5.png" />
<link rel="icon" href="https://seleniumbase.io/img/logo6.png" />
<h2 align="center"><a href="https://github.com/seleniumbase/SeleniumBase/"><img src="https://seleniumbase.io/cdn/img/sb_logo_10t.png" alt="SeleniumBase" title="SeleniumBase" width="248" /></a></h2>
<h4 align="center">Fast and reliable testing for web apps.</h4>
<h2 align="center"><a href="https://github.com/seleniumbase/SeleniumBase/"><img src="https://seleniumbase.io/cdn/img/sb_banner_t.png" alt="SeleniumBase" title="SeleniumBase" width="590" /></a></h2>
<h4 align="center">Create web & mobile tests 10x faster.</h4>
<p align="center"><a href="https://github.com/seleniumbase/SeleniumBase/releases">
<img src="https://img.shields.io/github/v/release/seleniumbase/SeleniumBase.svg?color=2277EE" alt="Latest Release on GitHub" /></a> <a href="https://pypi.org/project/seleniumbase/">
<img src="https://img.shields.io/pypi/v/seleniumbase.svg?color=00a0e0" alt="Latest Release on PyPI" /></a> <a href="https://pepy.tech/project/seleniumbase">
@ -18,7 +18,7 @@
<img src="https://badges.gitter.im/seleniumbase/SeleniumBase.svg" alt="SeleniumBase" /></a> <a href="https://seleniumbase.io">
<img src="https://img.shields.io/badge/docs-%20seleniumbase.io-22BBAA.svg" alt="SeleniumBase.io Docs" /></a></p>
<p align="center">SeleniumBase is a complete framework for web automation and end-to-end testing with <a href="https://docs.pytest.org/en/latest/index.html">pytest</a>.<br />The API simplifies <a href="https://www.selenium.dev/documentation/">Selenium</a>'s out-of-the-box API, leading to tests with more maintainable code. <br />SeleniumBase includes advanced features such as a realtime <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md">Dashboard</a>, a <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md">Recorder</a>, and more.</p>
<p align="center">SeleniumBase is a complete framework for web automation & end-to-end testing with <a href="https://docs.pytest.org/en/latest/index.html">pytest</a>.<br />The API simplifies <a href="https://www.selenium.dev/documentation/">Selenium</a>'s out-of-the-box API, which leads to more maintainable test code. <br />SeleniumBase includes advanced features such as a realtime <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md">Dashboard</a>, a <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md">Recorder</a>, & more.</p>
<p align="center">
<a href="#python_installation">🚀 Start</a> |
@ -65,7 +65,7 @@
pytest my_first_test.py --demo
```
<p align="left"><a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py"><img src="https://seleniumbase.io/cdn/gif/my_first_test_4.gif" alt="SeleniumBase Demo Mode" title="SeleniumBase Demo Mode" /></a></p>
<p align="left"><a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py"><img src="https://seleniumbase.io/cdn/gif/my_first_test_2.gif" width="480" alt="SeleniumBase Demo Mode" title="SeleniumBase Demo Mode" /></a></p>
```python
from seleniumbase import BaseCase
@ -190,7 +190,7 @@ pytest test_demo_site.py
> (Chrome is the default browser if not specified with ``--browser=BROWSER``. On Linux, ``--headless`` is the default behavior. You can also run in headless mode on any OS. If your Linux machine has a GUI and you want to see the web browser as tests run, add ``--headed`` or ``--gui``.)
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py"><img src="https://seleniumbase.io/cdn/gif/demo_page_1.gif" title="SeleniumBase Demo Page" width="450" /></a><br />
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py"><img src="https://seleniumbase.io/cdn/gif/demo_page_2.gif" title="SeleniumBase Demo Page" width="450" /></a><br />
🔵 <b>Here are more examples that you can run:</b>
@ -206,7 +206,7 @@ pytest test_swag_labs.py
pytest my_first_test.py --demo
```
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py"><img src="https://seleniumbase.io/cdn/gif/my_first_test_4.gif" alt="SeleniumBase Demo Mode" title="SeleniumBase Demo Mode" /></a>
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py"><img src="https://seleniumbase.io/cdn/gif/my_first_test_2.gif" alt="SeleniumBase Demo Mode" title="SeleniumBase Demo Mode" /></a>
* By default, **[CSS Selectors](https://www.w3schools.com/cssref/css_selectors.asp)** are used for finding page elements.
* If you're new to CSS Selectors, games like [CSS Diner](http://flukeout.github.io/) can help you learn.

View File

@ -18,7 +18,7 @@ importlib-metadata==4.8.1;python_version>="3.6"
bleach==4.1.0
lunr==0.6.0;python_version>="3.6"
nltk==3.6.4;python_version>="3.6"
watchdog==2.1.5;python_version>="3.6"
watchdog==2.1.6;python_version>="3.6"
mkdocs==1.2.2;python_version>="3.6"
mkdocs-material==7.3.0;python_version>="3.6"
mkdocs-exclude-search==0.5.2;python_version>="3.6"

View File

@ -34,7 +34,7 @@ Run an example test in Demo Mode: (highlight assertions)
pytest my_first_test.py --demo
```
<img src="https://seleniumbase.io/cdn/gif/my_first_test_4.gif" title="SeleniumBase Demo Mode" /><br />
<img src="https://seleniumbase.io/cdn/gif/my_first_test_2.gif" title="SeleniumBase Demo Mode" /><br />
Run a different example in Demo Mode:
@ -42,7 +42,7 @@ Run a different example in Demo Mode:
pytest test_swag_labs.py --demo
```
<img src="https://seleniumbase.io/cdn/gif/swag_demo_2.gif" /><br />
<img src="https://seleniumbase.io/cdn/gif/swag_demo_3.gif" /><br />
Run an example test in Headless Mode: (invisible browser)
@ -70,7 +70,7 @@ Run a test on the Demo Site to try many SeleniumBase methods:
pytest test_demo_site.py
```
<img src="https://seleniumbase.io/cdn/gif/demo_page_1.gif" title="SeleniumBase Demo Page" /><br />
<img src="https://seleniumbase.io/cdn/gif/demo_page_2.gif" title="SeleniumBase Demo Page" /><br />
Run tests multi-threaded using [n] threads:
@ -160,6 +160,12 @@ For more advanced run commands, such as using a proxy server, see [../help_docs/
--------
If you just need to perform some quick website verification on various devices, you can use the <a href="https://seleniumbase.io/devices/">SeleniumBase Device Farm</a>. Just plug in a website URL, and it will display how the website looks on four different devices:
<a href="https://seleniumbase.io/devices/"><img src="https://seleniumbase.io/cdn/img/github_demo.png" width="540" title="SeleniumBase Mobile Mode" /></a><br />
--------
To make things easier, here's a simple GUI program that allows you to run a few example tests by pressing a button:
```bash

View File

@ -12,12 +12,18 @@
pytest TEST_NAME.py --recorder -s
```
🔴 To add your own actions inside the test, you'll need to create a breakpoint somewhere inside of it:
🔴 To add your own actions inside the test, you'll need to create a breakpoint inside your test to activate Debug Mode:
```python
import ipdb; ipdb.set_trace()
```
🔴 You can also activate Debug Mode at the start of your test by adding ``--trace`` as a ``pytest`` command-line option:
```bash
pytest TEST_NAME.py --trace -s
```
🔴 Once you've reached the breakpoint, you can take control of the browser and add in any actions that you want recorded. When you are finished recording, type "``c``" on the command-line and press ``[Enter]`` to let the test continue from the breakpoint. After the test completes, a file called ``TEST_NAME_rec.py`` will be automatically created in the ``./recordings`` folder, which will include the actions performed by the test, and the manual actions that you added in. Below is a command-line notification:
```bash
@ -64,6 +70,8 @@ class RecorderTest(BaseCase):
<p>🔴 SeleniumBase <code>1.66.5</code> improves the algorithm for converting recorded actions into SeleniumBase code.</p>
<p>🔴 SeleniumBase <code>1.66.6</code> adds more selector options and improves the algorithm for converting recorded actions into SeleniumBase code.</p>
--------
<div>To learn more about SeleniumBase, check out the Docs Site:</div>

View File

@ -5,7 +5,7 @@ packaging>=21.0;python_version>="3.6"
typing-extensions>=3.10.0.2
setuptools>=44.1.1;python_version<"3.5"
setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"
setuptools>=58.1.0;python_version>="3.6"
setuptools>=58.2.0;python_version>="3.6"
setuptools-scm==5.0.2;python_version<"3.6"
setuptools-scm>=6.3.2;python_version>="3.6"
tomli>=1.2.1;python_version>="3.6"

View File

@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "1.66.5"
__version__ = "1.66.6"

View File

@ -604,6 +604,218 @@ def process_test_file(
seleniumbase_lines.append(command)
continue
# Handle self.check_if_unchecked(SELECTOR)
if not object_dict:
data = re.match(
r"""^(\s*)self\.check_if_unchecked"""
r"""\((r?['"][\S\s]+['"])\)([\S\s]*)"""
r"""$""",
line,
)
else:
data = re.match(
r"""^(\s*)self\.check_if_unchecked"""
r"""\(([\S]+)\)([\S\s]*)"""
r"""$""",
line,
)
if data:
whitespace = data.group(1)
selector = "%s" % data.group(2)
selector = remove_extra_slashes(selector)
page_selectors.append(selector)
comments = data.group(3)
command = """%sself.check_if_unchecked(%s)%s""" % (
whitespace,
selector,
comments,
)
if selector_dict:
if add_comments:
comments = " # %s" % selector
selector = optimize_selector(selector)
if selector in selector_dict.keys():
selector_object = selector_dict[selector]
changed.append(selector_object.split(".")[0])
command = """%sself.check_if_unchecked(%s)%s""" % (
whitespace,
selector_object,
comments,
)
if object_dict:
if not add_comments:
comments = ""
object_name = selector
if object_name in object_dict.keys():
selector_object = object_dict[object_name]
changed.append(object_name.split(".")[0])
command = """%sself.check_if_unchecked(%s)%s""" % (
whitespace,
selector_object,
comments,
)
seleniumbase_lines.append(command)
continue
# Handle self.uncheck_if_checked(SELECTOR)
if not object_dict:
data = re.match(
r"""^(\s*)self\.uncheck_if_checked"""
r"""\((r?['"][\S\s]+['"])\)([\S\s]*)"""
r"""$""",
line,
)
else:
data = re.match(
r"""^(\s*)self\.uncheck_if_checked"""
r"""\(([\S]+)\)([\S\s]*)"""
r"""$""",
line,
)
if data:
whitespace = data.group(1)
selector = "%s" % data.group(2)
selector = remove_extra_slashes(selector)
page_selectors.append(selector)
comments = data.group(3)
command = """%sself.uncheck_if_checked(%s)%s""" % (
whitespace,
selector,
comments,
)
if selector_dict:
if add_comments:
comments = " # %s" % selector
selector = optimize_selector(selector)
if selector in selector_dict.keys():
selector_object = selector_dict[selector]
changed.append(selector_object.split(".")[0])
command = """%sself.uncheck_if_checked(%s)%s""" % (
whitespace,
selector_object,
comments,
)
if object_dict:
if not add_comments:
comments = ""
object_name = selector
if object_name in object_dict.keys():
selector_object = object_dict[object_name]
changed.append(object_name.split(".")[0])
command = """%sself.uncheck_if_checked(%s)%s""" % (
whitespace,
selector_object,
comments,
)
seleniumbase_lines.append(command)
continue
# Handle self.select_if_unselected(SELECTOR)
if not object_dict:
data = re.match(
r"""^(\s*)self\.select_if_unselected"""
r"""\((r?['"][\S\s]+['"])\)([\S\s]*)"""
r"""$""",
line,
)
else:
data = re.match(
r"""^(\s*)self\.select_if_unselected"""
r"""\(([\S]+)\)([\S\s]*)"""
r"""$""",
line,
)
if data:
whitespace = data.group(1)
selector = "%s" % data.group(2)
selector = remove_extra_slashes(selector)
page_selectors.append(selector)
comments = data.group(3)
command = """%sself.select_if_unselected(%s)%s""" % (
whitespace,
selector,
comments,
)
if selector_dict:
if add_comments:
comments = " # %s" % selector
selector = optimize_selector(selector)
if selector in selector_dict.keys():
selector_object = selector_dict[selector]
changed.append(selector_object.split(".")[0])
command = """%sself.select_if_unselected(%s)%s""" % (
whitespace,
selector_object,
comments,
)
if object_dict:
if not add_comments:
comments = ""
object_name = selector
if object_name in object_dict.keys():
selector_object = object_dict[object_name]
changed.append(object_name.split(".")[0])
command = """%sself.select_if_unselected(%s)%s""" % (
whitespace,
selector_object,
comments,
)
seleniumbase_lines.append(command)
continue
# Handle self.unselect_if_selected(SELECTOR)
if not object_dict:
data = re.match(
r"""^(\s*)self\.unselect_if_selected"""
r"""\((r?['"][\S\s]+['"])\)([\S\s]*)"""
r"""$""",
line,
)
else:
data = re.match(
r"""^(\s*)self\.unselect_if_selected"""
r"""\(([\S]+)\)([\S\s]*)"""
r"""$""",
line,
)
if data:
whitespace = data.group(1)
selector = "%s" % data.group(2)
selector = remove_extra_slashes(selector)
page_selectors.append(selector)
comments = data.group(3)
command = """%sself.unselect_if_selected(%s)%s""" % (
whitespace,
selector,
comments,
)
if selector_dict:
if add_comments:
comments = " # %s" % selector
selector = optimize_selector(selector)
if selector in selector_dict.keys():
selector_object = selector_dict[selector]
changed.append(selector_object.split(".")[0])
command = """%sself.unselect_if_selected(%s)%s""" % (
whitespace,
selector_object,
comments,
)
if object_dict:
if not add_comments:
comments = ""
object_name = selector
if object_name in object_dict.keys():
selector_object = object_dict[object_name]
changed.append(object_name.split(".")[0])
command = """%sself.unselect_if_selected(%s)%s""" % (
whitespace,
selector_object,
comments,
)
seleniumbase_lines.append(command)
continue
# Handle self.switch_to_frame(SELECTOR)
if not object_dict:
data = re.match(
@ -1435,6 +1647,75 @@ def process_test_file(
seleniumbase_lines.append(command)
continue
# Handle self.drag_and_drop(SELECTOR, SELECTOR)
if not object_dict:
data = re.match(
r"""^(\s*)self\.drag_and_drop"""
r"""\((r?['"][\S\s]+['"]),\s?([\S\s]+)\)([\S\s]*)"""
r"""$""",
line,
)
else:
data = re.match(
r"""^(\s*)self\.drag_and_drop"""
r"""\(([\S]+),\s?([\S]+)\)([\S\s]*)"""
r"""$""",
line,
)
if data:
whitespace = data.group(1)
selector1 = "%s" % data.group(2)
selector1 = remove_extra_slashes(selector1)
page_selectors.append(selector1)
selector2 = "%s" % data.group(3)
selector2 = remove_extra_slashes(selector2)
page_selectors.append(selector2)
comments = data.group(4)
command = """%sself.drag_and_drop(%s, %s)%s""" % (
whitespace,
selector1,
selector2,
comments,
)
if selector_dict:
if add_comments:
comments = " # %s" % selector
selector1 = optimize_selector(selector1)
selector2 = optimize_selector(selector2)
if selector1 in selector_dict.keys() and (
selector2 in selector_dict.keys()
):
selector_object1 = selector_dict[selector1]
selector_object2 = selector_dict[selector2]
changed.append(selector_object1.split(".")[0])
changed.append(selector_object2.split(".")[0])
command = """%sself.drag_and_drop(%s, %s)%s""" % (
whitespace,
selector_object1,
selector_object2,
comments,
)
if object_dict:
if not add_comments:
comments = ""
object_name1 = selector1
object_name2 = selector2
if object_name1 in object_dict.keys() and (
object_name2 in object_dict.keys()
):
selector_object1 = object_dict[object_name1]
selector_object2 = object_dict[object_name2]
changed.append(object_name1.split(".")[0])
changed.append(object_name2.split(".")[0])
command = """%sself.drag_and_drop(%s, %s)%s""" % (
whitespace,
selector_object1,
selector_object2,
comments,
)
seleniumbase_lines.append(command)
continue
# Handle self.hover_and_click(SELECTOR, SELECTOR)
if not object_dict:
data = re.match(

Binary file not shown.

View File

@ -1827,6 +1827,16 @@ class BaseCase(unittest.TestCase):
self.scroll_to(hover_selector, by=hover_by)
pre_action_url = self.driver.current_url
pre_window_count = len(self.driver.window_handles)
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
origin = self.get_origin()
the_selectors = [hover_selector, click_selector]
action = ["ho_cl", the_selectors, origin, time_stamp]
self.__extra_actions.append(action)
outdated_driver = False
element = None
try:
@ -3276,6 +3286,17 @@ class BaseCase(unittest.TestCase):
origin = origin[0:-1]
if origin not in origins:
origins.append(origin)
for n in range(len(srt_actions)):
if (
srt_actions[n][0] == "click"
and n > 0
and srt_actions[n-1][0] == "ho_cl"
and srt_actions[n-1][2] in origins
):
srt_actions[n-1][0] = "_skip"
srt_actions[n][0] = "h_clk"
srt_actions[n][1] = srt_actions[n-1][1][0]
srt_actions[n][2] = srt_actions[n-1][1][1]
ext_actions = []
ext_actions.append("js_cl")
ext_actions.append("as_el")

View File

@ -101,6 +101,8 @@ var getBestSelector = function(el) {
non_id_attributes.push('data-test-id');
non_id_attributes.push('data-test-selector');
non_id_attributes.push('data-nav');
non_id_attributes.push('data-sb');
non_id_attributes.push('data-cy');
non_id_attributes.push('data-action');
non_id_attributes.push('data-target');
non_id_attributes.push('alt');
@ -114,6 +116,7 @@ var getBestSelector = function(el) {
non_id_attributes.push('value');
non_id_attributes.push('ng-model');
non_id_attributes.push('ng-if');
non_id_attributes.push('src');
selector_by_attr = [];
all_by_attr = [];
num_by_attr = [];
@ -149,9 +152,13 @@ var getBestSelector = function(el) {
contains_tags.push('h2');
contains_tags.push('h3');
contains_tags.push('h4');
contains_tags.push('li');
contains_tags.push('td');
contains_tags.push('code');
contains_tags.push('mark');
contains_tags.push('label');
contains_tags.push('button');
contains_tags.push('legend');
contains_tags.push('strong');
all_by_tag = [];
inner_text = el.innerText.trim();

View File

@ -121,7 +121,7 @@ setup(
"typing-extensions>=3.10.0.2",
'setuptools>=44.1.1;python_version<"3.5"',
'setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"',
'setuptools>=58.1.0;python_version>="3.6"',
'setuptools>=58.2.0;python_version>="3.6"',
'setuptools-scm==5.0.2;python_version<"3.6"',
'setuptools-scm>=6.3.2;python_version>="3.6"',
'tomli>=1.2.1;python_version>="3.6"',