Make improvements to Recorder Mode assertions

This commit is contained in:
Michael Mintz 2021-10-14 16:38:42 -04:00
parent 17fe3adc9d
commit eff3cd25b9
3 changed files with 48 additions and 64 deletions

View File

@ -18,15 +18,13 @@ pytest TEST_NAME.py --rec -s
import ipdb; ipdb.set_trace()
```
🔴 The Recorder will capture browser actions on URLs that begin with ``https:``, ``http:``, and ``file:``. (The Recorder won't work on ``data:`` URLS.)
🔴 You can also activate Debug Mode at the start of your test by adding ``--trace`` as a ``pytest`` command-line option:
🔴 You can also activate Debug Mode at the start of your test by adding ``--trace`` as a ``pytest`` command-line option: (This is useful when running Recorder Mode without any breakpoints.)
```bash
pytest TEST_NAME.py --trace --rec -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:
🔴 Once you've reached the breakpoint, you can take control of the browser and add in any actions that you want recorded. The Recorder will capture browser actions on URLs that begin with ``https:``, ``http:``, and ``file:``; (the Recorder won't work on ``data:`` URLS). 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
>>> RECORDING SAVED as: recordings/my_first_test_rec.py
@ -52,7 +50,7 @@ class RecorderTest(BaseCase):
import ipdb; ipdb.set_trace()
```
<p>🔴 The above file gives you a basic SeleniumBase file with a breakpoint in it so that you can immediately start recording after you've opened a new web page in the browser.</p>
<p>🔴 The above code gives you a basic SeleniumBase file with a breakpoint in it so that you can immediately start recording after you've opened a new web page in the browser.</p>
<p>🔴 Recorder Mode works by saving your recorded actions into the browser's <code>sessionStorage</code>. SeleniumBase then reads from the browser's <code>sessionStorage</code> to take the raw data and generate a full test from it. Keep in mind that <code>sessionStorage</code> is only present for a website while the browser tab remains on a web page of the same domain/origin. If you leave that domain/origin, the <code>sessionStorage</code> of that tab will no longer have the raw data that SeleniumBase needs to create a full recording. To compensate for this, all links to web pages of a different domain/origin will automatically open a new tab for you while in Recorder Mode. Additionally, the SeleniumBase <code>self.open(URL)</code> method will also open a new tab for you in Recorder Mode if the domain/origin is different from the current URL. When the recorded test completes, SeleniumBase will scan the <code>sessionStorage</code> of all open browser tabs for the data it needs to generate the complete SeleniumBase automation script.</p>
@ -66,26 +64,16 @@ class RecorderTest(BaseCase):
<p>🔴 SeleniumBase <code>1.66.2</code> adds the ability to save selectors using the <code>":contains(TEXT)"</code> selector. If a Python file being recorded has multiple tests being run, then all those tests will get saved to the generated <code>*_rec.py</code> file. The Recorder will now save common <code>self.assert_*</code> calls made during tests. In order to escape iframes when using <code>self.set_content_to_frame(frame)</code>, a new method was added: <code>self.set_content_to_default()</code>. The <code>self.set_content_to_*()</code> methods will be automatically used in place of <code>self.switch_to_*()</code> methods in Recorder Mode, unless a test explicitly calls <code>self._rec_overrides_switch = False</code> before the <code>self.switch_to_*()</code> methods are called. Additionally, if an iframe contains the <code>src</code> attribute, that page will get loaded in a new tab when switching to it in Recorder Mode.</p>
<p>🔴 SeleniumBase <code>1.66.3</code> improves the algorithm for generating efficient selectors. Additionally, single and double quotes in selectors will now be properly escaped with backslashes as needed.</p>
<p>🔴 SeleniumBase versions <code>1.66.3</code>, <code>1.66.4</code>, <code>1.66.5</code>, <code>1.66.6</code>, <code>1.66.7</code>, <code>1.66.8</code>, and <code>1.66.9</code> improve the algorithm for converting recorded actions into SeleniumBase code.</p>
<p>🔴 SeleniumBase <code>1.66.4</code> adds better error-handling for the Recorder, and a few other improvements.</p>
<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> improves the algorithm for converting recorded actions into SeleniumBase code.</p>
<p>🔴 SeleniumBase <code>1.66.7</code> improves Recorder Mode post-processing and automatically adds <code>self.show_file_choosers()</code> if file-upload fields are hidden when calling <code>self.choose_file(selector, file_path)</code>.</p>
<p>🔴 SeleniumBase <code>1.66.8</code> generates better selectors in Recorder Mode and improves the algorithm for converting recorded actions into SeleniumBase code.</p>
<p>🔴 SeleniumBase <code>1.66.9</code> allows the Recorder to record methods related to file downloads. It also updates how the Recorder handles class-parsing and form submissions.</p>
<p>🔴 SeleniumBase <code>1.66.10</code> adds better error-handling for the Recorder. It also adds the console script option <code>-r</code> for <code>sbase mkfile</code> to generate a new test file with a breakpoint for Recorder Mode: <code>sbase mkfile NEW_FILE.py -r</code></p>
<p>🔴 SeleniumBase <code>1.66.10</code> adds better error-handling to the Recorder. It also adds the console script option <code>-r</code> for <code>sbase mkfile</code> to generate a new test file with a breakpoint for Recorder Mode: <code>sbase mkfile NEW_FILE.py -r</code></p>
<p>🔴 SeleniumBase <code>1.66.12</code> adds the ability to instantly create a new test recording by running <code>sbase mkrec FILE.py</code>. Once the browser spins up, you can open a new web page and start performing actions that will get recorded and saved to the file you specified.</p>
<p>🔴 SeleniumBase <code>1.66.13</code> lets you add assertions for elements and text while making a recording. To add an element assertion, press the <code>[^]-key (SHIFT+6)</code>, (the border will become purple) then click on elements that you'd like to assert. To add a text assertion, press the <code>[&]-key (SHIFT+7)</code>, (the border will become orange) then click on text elements that you'd like to assert. To go back to the regular Record Mode, press any other key. While in the special assertion modes, certain actions such as clicking on links won't have any effect. This lets you make assertions on elements without certain actions getting in the way.</p>
<p>🔴 SeleniumBase <code>1.66.14</code> improves the algorithm for converting recorded assertions into SeleniumBase code. Text assertions that contain the newline character will now be handled correctly. If a text assertion has a <code>:contains</code> selector, then the text assertion will be changed to an element assertion. Asserted text from multi-line assertions with use <code>self.assert_text()</code> while asserted text from single-line assertions will use <code>self.assert_exact_text()</code>.</p>
--------
<div>To learn more about SeleniumBase, check out the Docs Site:</div>

Binary file not shown.

View File

@ -99,10 +99,7 @@ var ssOccurrences = function(string, subString, allowOverlapping) {
var step = allowOverlapping ? 1 : subString.length;
while (true) {
pos = string.indexOf(subString, pos);
if (pos >= 0) {
++n;
pos += step;
}
if (pos >= 0) { ++n; pos += step; }
else break;
}
return n;
@ -156,8 +153,9 @@ var getBestSelector = function(el) {
child_count_by_attr = [];
for (var i = 0; i < non_id_attributes.length; i++) {
selector_by_attr[i] = null;
if (non_id_attributes[i] == 'class')
if (non_id_attributes[i] == 'class') {
selector_by_attr[i] = selector_by_class;
}
else {
selector_by_attr[i] = cssPathByAttribute(el, non_id_attributes[i]);
}
@ -174,6 +172,7 @@ var getBestSelector = function(el) {
basic_tags.push('h1');
basic_tags.push('h2');
basic_tags.push('h3');
basic_tags.push('center');
basic_tags.push('input');
basic_tags.push('textarea');
for (var i = 0; i < basic_tags.length; i++) {
@ -211,8 +210,8 @@ var getBestSelector = function(el) {
t_count += 1;
}
if (t_count === 1 && !inner_text.includes('\n')) {
inner_text = inner_text.replace("'", "\\'");
inner_text = inner_text.replace('"', '\\"');
inner_text = inner_text.replaceAll("'", "\\'");
inner_text = inner_text.replaceAll('"', '\\"');
return tag_name += ':contains("'+inner_text+'")';
}
}
@ -227,7 +226,8 @@ var getBestSelector = function(el) {
qsa_element = "span";
if (parent_tag_name == "button")
qsa_element = "button > span";
else { qsa_element = "button > "+parent_tag_name+" > span" }
else
qsa_element = "button > "+parent_tag_name+" > span";
t_count = 0;
all_el_found = document.querySelectorAll(qsa_element);
for (var j = 0; j < all_el_found.length; j++) {
@ -235,8 +235,8 @@ var getBestSelector = function(el) {
t_count += 1;
}
if (t_count === 1 && !inner_text.includes('\n')) {
inner_text = inner_text.replace("'", "\\'");
inner_text = inner_text.replace('"', '\\"');
inner_text = inner_text.replaceAll("'", "\\'");
inner_text = inner_text.replaceAll('"', '\\"');
return qsa_element += ':contains("'+inner_text+'")';
}
}
@ -299,30 +299,15 @@ var reset_recorder_state = function() {
sessionStorage.setItem('pause_recorder', 'no');
sessionStorage.setItem('recorder_mode', '1');
const d_now = Date.now();
w_orig = window.location.origin;
w_href = window.location.href;
if (sessionStorage.getItem('recorder_activated') === 'yes') {
ss_ra = JSON.parse(sessionStorage.getItem('recorded_actions'));
document.recorded_actions = ss_ra;
w_orig = window.location.origin;
w_href = window.location.href;
ra_len = document.recorded_actions.length;
if (ra_len > 0 && document.recorded_actions[ra_len-1][0] === 'begin') {
document.recorded_actions.pop();
document.recorded_actions.push(['begin', w_orig, w_href, d_now]);
}
else if (ra_len > 0 &&
document.recorded_actions[ra_len-1][0] === '_url_')
{
document.recorded_actions.pop();
document.recorded_actions.push(['_url_', w_orig, w_href, d_now]);
}
else {
document.recorded_actions.push(['_url_', w_orig, w_href, d_now]);
}
}
else {
sessionStorage.setItem('recorder_activated', 'yes');
w_orig = window.location.origin;
w_href = window.location.href;
document.recorded_actions.push(['begin', w_orig, w_href, d_now]);
}
json_rec_act = JSON.stringify(document.recorded_actions);
@ -368,8 +353,9 @@ document.body.addEventListener('dragstart', function (event) {
{
document.recorded_actions.pop();
}
if (element.draggable === true)
if (element.draggable === true) {
document.recorded_actions.push(['drags', selector, '', d_now]);
}
json_rec_act = JSON.stringify(document.recorded_actions);
sessionStorage.setItem('recorded_actions', json_rec_act);
});
@ -450,7 +436,7 @@ document.body.addEventListener('change', function (event) {
document.recorded_actions[ra_len-1][1] === selector &&
tag_name === 'input' && e_type === 'checkbox')
{
// The checkbox state only needs to be set once. (Pop duplicates.)
// Pop duplicate checkbox state changes.
document.recorded_actions.pop();
ra_len = document.recorded_actions.length;
if (ra_len > 0 && document.recorded_actions[ra_len-1][1] === selector)
@ -474,15 +460,13 @@ document.body.addEventListener('mousedown', function (event) {
const selector = getBestSelector(element);
ra_len = document.recorded_actions.length;
tag_name = element.tagName.toLowerCase();
if (ra_len > 0 && document.recorded_actions[ra_len-1][0] === 'mo_dn') {
if (ra_len > 0 && document.recorded_actions[ra_len-1][0] === 'mo_dn')
document.recorded_actions.pop();
}
if (tag_name === 'select') {
// Do Nothing. (Handle select in 'change' action.)
}
else {
else
document.recorded_actions.push(['mo_dn', selector, '', d_now]);
}
json_rec_act = JSON.stringify(document.recorded_actions);
sessionStorage.setItem('recorded_actions', json_rec_act);
});
@ -506,17 +490,32 @@ document.body.addEventListener('mouseup', function (event) {
document.recorded_actions[ra_len-1][0] === 'mo_dn' &&
document.recorded_actions[ra_len-1][1] === selector)
{
if (rec_mode === '2') {
sel_has_contains = selector.includes(':contains(');
if (rec_mode === '2' || (rec_mode === '3' && sel_has_contains)) {
origin = window.location.origin;
document.recorded_actions.push(['as_el', selector, origin, d_now]);
return;
}
else if (rec_mode === '3') {
origin = window.location.origin;
text = element.textContent;
text = element.innerText;
action = 'as_et';
if (!text) { text = ''; }
else {
text = text.trim();
var match = /\r|\n/.exec(text);
if (match) {
lines = text.split(/\r\n|\r|\n/g);
text = '';
for (var i = 0; i < lines.length; i++) {
if (lines[i].length > 0) {
action = 'as_te'; text = lines[i]; break;
}
}
}
}
tex_sel = [text, selector];
document.recorded_actions.push(['as_te', tex_sel, origin, d_now]);
document.recorded_actions.push([action, tex_sel, origin, d_now]);
return;
}
}
@ -566,16 +565,13 @@ document.body.addEventListener('mouseup', function (event) {
child_count > 2 && !grand_element.hasAttribute('onclick')))
{
w_orig = window.location.origin;
if (origin === w_orig) {
if (origin === w_orig)
document.recorded_actions.push(['_url_', origin, href, d_now]);
}
else {
else
document.recorded_actions.push(['begin', origin, href, d_now]);
}
}
else {
else
document.recorded_actions.push(['click', selector, href, d_now]);
}
// Switch to hover_click() if in a dropdown.
if (element.parentElement.classList.contains('dropdown-content') &&
element.parentElement.parentElement.classList.contains('dropdown'))
@ -603,7 +599,7 @@ document.body.addEventListener('mouseup', function (event) {
else if (ra_len > 0 &&
document.recorded_actions[ra_len-1][0] === 'mo_dn')
{
// Probably an accidental drag & drop action.
// Probably an accidental drag & drop.
document.recorded_actions.pop();
}
json_rec_act = JSON.stringify(document.recorded_actions);
@ -644,7 +640,7 @@ document.body.addEventListener('keydown', function (event) {
document.body.addEventListener('keyup', function (event) {
if (typeof document.recorded_actions === 'undefined')
reset_recorder_state();
// Controls for Pausing and Resuming the Recorder.
// Controls for Pausing & Resuming.
pause_rec = sessionStorage.getItem('pause_recorder');
if (event.key.toLowerCase() === 'escape' && pause_rec === 'no')
{