Unknown marker with pytest-bdd only when parameter is declared - pytest

When I declare a marker in pytest.ini having a parameter, this is not recognized in pytest-bdd feature file. Markers without parameters seem to work fine.
[pytest]
markers =
swr(issue1): link to Software Requirement
smoke: Smoke Test component
Simple feature file works fine with #smoke:
Feature: Trivial Example
#smoke
Scenario: Add a number to another number
Given 7 is set
When 9 is added
Then new value is 16
Fails with #swr("123"):
Feature: Trivial Example
#swr("123")
Scenario: Add a number to another number
Given 7 is set
When 9 is added
Then new value is 16
Failure is a warning:
../../../../../.local/lib/python3.10/site-packages/pytest_bdd/plugin.py:127
/home/parallels/.local/lib/python3.10/site-packages/pytest_bdd/plugin.py:127: PytestUnknownMarkWarning: Unknown pytest.mark.swr("123") - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
mark = getattr(pytest.mark, tag)

Taking a look in the known issues in the repository I stumbled upon something that is related. The developer mentions there is a hook available which is seen here.
In the conftest.py we can then do the following:
from typing import Callable, cast
import pytest
import ast
def pytest_bdd_apply_tag(tag: str, function) -> Callable:
tree = ast.parse(tag)
body = tree.body[0].value
if isinstance(body, ast.Call):
name = body.func.id
arg = body.args[0].value
mark = getattr(pytest.mark, name).with_args(arg)
else:
mark = getattr(pytest.mark, tag)
marked = mark(function)
return cast(Callable, marked)
Then we can just register the marker as swr and the hook should automatically parametrize the function as needed. It uses ast to parse the marker and dynamically create the new marker. Shown below is what mark looks like when running with swr or swr("123").
platform darwin -- Python 3.9.6, pytest-7.2.0, pluggy-1.0.0
rootdir: ***, configfile: pytest.ini
plugins: bdd-6.1.1
collecting ...
MarkDecorator(mark=Mark(name='swr', args=('123',), kwargs={}))
collected 1 item
platform darwin -- Python 3.9.6, pytest-7.2.0, pluggy-1.0.0
rootdir: ***, configfile: pytest.ini
plugins: bdd-6.1.1
collecting ...
MarkDecorator(mark=Mark(name='swr', args=(), kwargs={}))
collected 1 item
Take note of MarkDecorator in the output for each of the calls.

Related

Stop huge error output from testing-library

I love testing-library, have used it a lot in a React project, and I'm trying to use it in an Angular project now - but I've always struggled with the enormous error output, including the HTML text of the render. Not only is this not usually helpful (I couldn't find an element, here's the HTML where it isn't); but it gets truncated, often before the interesting line if you're running in debug mode.
I simply added it as a library alongside the standard Angular Karma+Jasmine setup.
I'm sure you could say the components I'm testing are too large if the HTML output causes my console window to spool for ages, but I have a lot of integration tests in Protractor, and they are SO SLOW :(.
I would say the best solution would be to use the configure method and pass a custom function for getElementError which does what you want.
You can read about configuration here: https://testing-library.com/docs/dom-testing-library/api-configuration
An example of this might look like:
configure({
getElementError: (message: string, container) => {
const error = new Error(message);
error.name = 'TestingLibraryElementError';
error.stack = null;
return error;
},
});
You can then put this in any single test file or use Jest's setupFiles or setupFilesAfterEnv config options to have it run globally.
I am assuming you running jest with rtl in your project.
I personally wouldn't turn it off as it's there to help us, but everyone has a way so if you have your reasons, then fair enough.
1. If you want to disable errors for a specific test, you can mock the console.error.
it('disable error example', () => {
const errorObject = console.error; //store the state of the object
console.error = jest.fn(); // mock the object
// code
//assertion (expect)
console.error = errorObject; // assign it back so you can use it in the next test
});
2. If you want to silence it for all the test, you could use the jest --silent CLI option. Check the docs
The above might even disable the DOM printing that is done by rtl, I am not sure as I haven't tried this, but if you look at the docs I linked, it says
"Prevent tests from printing messages through the console."
Now you almost certainly have everything disabled except the DOM recommendations if the above doesn't work. On that case you might look into react-testing-library's source code and find out what is used for those print statements. Is it a console.log? is it a console.warn? When you got that, just mock it out like option 1 above.
UPDATE
After some digging, I found out that all testing-library DOM printing is built on prettyDOM();
While prettyDOM() can't be disabled you can limit the number of lines to 0, and that would just give you the error message and three dots ... below the message.
Here is an example printout, I messed around with:
TestingLibraryElementError: Unable to find an element with the text: Hello ther. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
...
All you need to do is to pass in an environment variable before executing your test suite, so for example with an npm script it would look like:
DEBUG_PRINT_LIMIT=0 npm run test
Here is the doc
UPDATE 2:
As per the OP's FR on github this can also be achieved without injecting in a global variable to limit the PrettyDOM line output (in case if it's used elsewhere). The getElementError config option need to be changed:
dom-testing-library/src/config.js
// called when getBy* queries fail. (message, container) => Error
getElementError(message, container) {
const error = new Error(
[message, prettyDOM(container)].filter(Boolean).join('\n\n'),
)
error.name = 'TestingLibraryElementError'
return error
},
The callstack can also be removed
You can change how the message is built by setting the DOM testing library message building function with config. In my Angular project I added this to test.js:
configure({
getElementError: (message: string, container) => {
const error = new Error(message);
error.name = 'TestingLibraryElementError';
error.stack = null;
return error;
},
});
This was answered here: https://github.com/testing-library/dom-testing-library/issues/773 by https://github.com/wyze.

Use of undeclared unstable module when module is imported and a cfg attribute is used

I have the below code:
#[cfg(all(feature = "unstable", unique))]
#[cfg(all(feature = "unstable", heap_api))]
#[cfg(all(feature = "unstable", alloc))]
use std::ptr::Unique;
use std::mem;
use alloc::heap;
pub struct Foo<T> {
arr: Unique<T>,
cap: usize,
probe_limit: usize,
}
However, when I try to compile it with cargo build --features "unstable" I get a compilation error. Note that I am using the nightly build of Rust and the unstable feature is set up correctly (otherwise I would get a different error).
error[E0412]: cannot find type `Unique` in this scope
--> src/hash/arr.rs:27:8
|
27 | arr: Unique<T>,
| ^^^^^^ not found in this scope
|
help: possible candidate is found in another module, you can import it into scope
| use std::ptr::Unique;
I'm not sure why Unique is not found. I am should be using it at the top of my file. use ::std::ptr::Unique does not work.
Let's deconstruct your cfg instruction:
#[cfg(all(feature = "unstable", unique))]
This is an outer attribute, meaning it is outside the item it will change, and will apply to the next item. With the cfg attribute, it means "if the feature inside the parenthesis is enabled, do the next block", and all is a AND between the features. So you have something like "if features unstable and unique are enabled, do the next statement.
Attributes! What you want is to have the inner attribute feature(unique) conditionally if you have set the feature unstable on the command line. A conditional attribute can be obtained with cfg_attr.
#![cfg_attr(feature = "unstable", feature(unique))]
This can be read as if feature unstable is enabled, then enable the inner feature(unique). Then you will be able to use std::ptr::Unique.
You should also add a #[cfg(feature = "unstable")] before the use and your struct, so that they won't be usable if the feature is not enabled.

Python w/QT Creator form - Possible to grab multiple values?

I'm surprised to not find a previous question about this, but I did give an honest try before posting.
I've created a ui with Qt Creator which contains quite a few QtWidgets of type QLineEdit, QTextEdit, and QCheckbox. I've used pyuic5 to convert to a .py file for use in a small python app. I've successfully got the form connected and working, but this is my first time using python with forms.
I'm searching to see if there is a built-in function or object that would allow me to pull the ObjectNames and Values of all widgets contained within the GUI form and store them in a dictionary with associated keys:values, because I need to send off the information for post-processing.
I guess something like this would work manually:
...
dict = []
dict['checkboxName1'] = self.checkboxName1.isChecked()
dict['checkboxName2'] = self.checkboxName2.isChecked()
dict['checkboxName3'] = self.checkboxName3.isChecked()
dict['checkboxName4'] = self.checkboxName4.isChecked()
dict['lineEditName1'] = self.lineEditName1.text()
... and on and on
But is there a way to grab all the objects and loop through them, even if each different type (i.e. checkboxes, lineedits, etc) needs to be done separately?
I hope I've explained that clearly.
Thank you.
Finally got it working. Couldn't find a python specific example anywhere, so through trial and error this worked perfectly. I'm including the entire working code of a .py file that can generate a list of all QCheckBox objectNames on a properly referenced form.
I named my form main_form.ui from within Qt Creator. I then converted it into a .py file with pyuic5
pyuic5 main_form.ui -o main_form.py
This is the contents of a sandbox.py file:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
import main_form
# the name of my Qt Creator .ui form converted to main_form.py with pyuic5
# pyuic5 original_form_name_in_creator.ui -o main_form.py
class MainApp(QtWidgets.QMainWindow, main_form.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
# Push button object on main_form named btn_test
self.btn_test.clicked.connect(self.runTest)
def runTest(self):
# I believe this creates a List of all QCheckBox objects on entire UI page
c = self.findChildren(QtWidgets.QCheckBox)
# This is just to show how to access objectName property as an example
for box in c:
print(box.objectName())
def main():
app = QtWidgets.QApplication(sys.argv) # A new instance of QApplication
form = MainApp() # We set the form to be our ExampleApp (design)
form.show() # Show the form
app.exec_() # and execute the app
if __name__ == '__main__': # if we're running file directly and not importing it
main() # run the main function
See QObject::findChildren()
In C++ the template argument would allow one to specify which type of widget to retrieve, e.g. to just retrieve the QLineEdit objects, but I don't know if or how that is mapped into Python.
Might need to retrieve all types and then switch handling while iterating over the resulting list.

Collect py.test tests info with markers

I'm using py.test and I want to get the list of tests that I have with marker info included.
When I use the --collect-only flag I get the test functions. Is there a way to get the assigned markers for each test also?
Based on Frank T's answer I created a workaround code sample:
from _pytest.mark import MarkInfo, MarkDecorator
import json
def pytest_addoption(parser):
parser.addoption(
'--collect-only-with-markers',
action='store_true',
help='Collect the tests with marker information without executing them'
)
def pytest_collection_modifyitems(session, config, items):
if config.getoption('--collect-only-with-markers'):
for item in items:
data = {}
# Collect some general information
if item.cls:
data['class'] = item.cls.__name__
data['name'] = item.name
if item.originalname:
data['originalname'] = item.originalname
data['file'] = item.location[0]
# Get the marker information
for key, value in item.keywords.items():
if isinstance(value, (MarkDecorator, MarkInfo)):
if 'marks' not in data:
data['marks'] = []
data['marks'].append(key)
print(json.dumps(data))
# Remove all items (we don't want to execute the tests)
items.clear()
I don't think pytest has built-in behavior to list test functions along with the marker information for those tests. A --markers command lists all registered markers, but that's not what you want. I briefly looked over the list of pytest plugins and didn't see anything that looked relevant.
You can write your own pytest plugin to list tests along with marker info. Here is documentation on writing a pytest plugin.
I would try using the "pytest_collection_modifyitems" hook. It is passed a list of all tests that are collected, and it doesn't need to modify them. (Here is a list of all hooks.)
The tests passed in to that hook have a get_marker() method if you know the name of the marker you're looking for (see this code for example). When I was looking through that code, I could not find an official API for listing all markers. I found this to get the job done: test.keywords.__dict__['_markers'] (see here and here).
You can find markers by a name attribute in the request.function.pytestmark object
#pytest.mark.scenarious1
#pytest.mark.scenarious2
#pytest.mark.scenarious3
def test_sample():
pass
#pytest.fixture(scope='function',autouse=True)
def get_markers():
print([marker.name for marker in request.function.pytestmark])
>>> ['scenarious3', 'scenarious2', 'scenarious1']
Note, that they were listed in the reversed order by default.

How to get PyTest fixtures to autocomplete in PyCharm (type hinting)

I had a bear of a time figuring this out, and it was really bugging me, so I thought I'd post this here in case anyone hit the same problem...
(and the answer is so dang simple it hurts :-)
The Problem
The core of the issue is that sometimes, not always, when dealing with fixtures in PyTest that return objects, when you use those fixtures in a test in PyCharm, you don't get autocomplete hints. If you have objects with large numbers of methods you want to reference while writing a test, this can add a lot of overhead and inconvenience to the test writing process.
Here's a simple example to illustrate the issue:
Let's say I've got a class "event_manager" that lives in:
location.game.events
Let's further say that in my conftest.py file (PyTest standard thing for the unfamiliar), I've got a fixture that returns an instance of that class:
from location.game.events import event_manager
...
#pytest.fixture(scope="module")
def event_mgr():
"""Creates a new instance of event generate for use in tests"""
return event_manager()
I've had issues sometimes, (but not always - I can't quite figure out why) with classes like this where autocomplete will not work properly in the test code where I use the fixture, e.g.
def test_tc10657(self, evt_mgr):
"""Generates a Regmod and expects filemod to be searchable on server"""
evt_mgr.(This does not offer autocomplete hints when you type ".")
So the answer is actually quite simple, once you review type hinting in PyCharm:
http://www.jetbrains.com/help/pycharm/2016.1/type-hinting-in-pycharm.html
Here's how to fix the above test code so that autocomplete works properly:
from location.game.events import event_manager
...
def test_tc10657(self, evt_mgr: event_manager):
"""Generates a Regmod and expects filemod to be searchable on server"""
evt_mgr.(This DOES offer hints when you type "." Yay!)
Notice how I explicitly type the fixture as an input parameter of type event_manager.
Also if you add a docstring to a function and specify the type of the the parameters, you will get the code completion for those parameters.
For example using pytest and Selenium:
# The remote webdriver seems to be the base class for the other webdrivers
from selenium.webdriver.remote.webdriver import WebDriver
def test_url(url, browser_driver):
"""
This method is used to see if IBM is in the URL title
:param WebDriver browser_driver: The browser's driver
:param str url: the URL to test
"""
browser_driver.get(url)
assert "IBM" in browser_driver.title
Here's my conftest.py file as well
import pytest
from selenium import webdriver
# Method to handle the command line arguments for pytest
def pytest_addoption(parser):
parser.addoption("--driver", action="store", default="chrome", help="Type in browser type")
parser.addoption("--url", action="store", default='https://www.ibm.com', help="url")
#pytest.fixture(scope='module', autouse=True)
def browser_driver(request):
browser = request.config.getoption("--driver").lower()
# yield the driver to the specified browser
if browser == "chrome":
driver = webdriver.Chrome(executable_path='/path/to/chromedriver')
else:
raise Exception("No driver for browser " + browser)
yield driver
driver.quit()
#pytest.fixture(scope="module")
def url(request):
return request.config.getoption("--url")
Tested using Python 2.7 and PyCharm 2017.1. The docstring format is reStructuredText and the "Analyze Python code in docstrings" checkbox is checked in settings.