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

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.

Related

Pytest + Appium test framework

I'm very new to automation development, and currently starting to write an appium+pytest based Android app testing framework.
I managed to run tests on a connected device using this code, that seems to use unittest:
class demo(unittest.TestCase):
reportDirectory = 'reports'
reportFormat = 'xml'
dc = {}
driver = None
# testName = 'test_setup_tmotg_demo'
def setUp(self):
self.dc['reportDirectory'] = self.reportDirectory
self.dc['reportFormat'] = self.reportFormat
# self.dc['testName'] = self.testName
self.dc['udid'] = 'RF8MA2GW1ZF'
self.dc['appPackage'] = 'com.tg17.ud.internal'
self.dc['appActivity'] = 'com.tg17.ud.ui.splash.SplashActivity'
self.dc['platformName'] = 'android'
self.dc['noReset'] = 'true'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub',self.dc)
# def test_function1():
# code
# def test_function2():
# code
# def test_function3():
# code
# etc...
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main()
As you can see all the functions are currently within 'demo' class.
The intention is to create several test cases for each part of the app (for example: registration, main screen, premium subscription, etc.). That could sum up to hundreds of test cases eventually.
It seems to me that simply continuing listing them all in this same class would be messy and would give me a very limited control. However I didn't find any other way to arrange my tests while keeping the device connected via appium.
The question is what would be the right way to organize the project so that I can:
Set up the device with appium server
Run all the test suites in sequential order (registration, main screen, subscription, etc...).
Perform the cleaning... export results, disconnect device, etc.
I hope I described the issue clearly enough. Would be happy to elaborate if needed.
Well you have a lot of questions here so it might be good to split them up into separate threads. But first of all you can learn a lot about how Appium works by checking out the documentation here. And for the unittest framework here.
All Appium cares about is the capabilities file (or variable). So you can either populate it manually or white some helper function to do that for you. Here is a list of what can be used.
You can create as many test classes(or suites) as you want and add them together in any order you wish. This helps to break things up into manageable chunks. (See example below)
You will have to create some helper methods here as well, since Appium itself will not do much cleaning. You can use the adb command in the shell for managing android devices.
import unittest
from unittest import TestCase
# Create a Base class for common methods
class BaseTest(unittest.TestCase):
# setUpClass method will only be ran once, and not every suite/test
#classmethod
def setUpClass(cls) -> None:
# Init your driver and read the capabilites here
pass
#classmethod
def tearDownClass(cls) -> None:
# Do cleanup, close the driver, ...
pass
# Use the BaseTest class from before
# You can then duplicate this class for other suites of tests
class TestLogin(BaseTest):
#classmethod
def setUpClass(cls) -> None:
super(TestLogin, cls).setUpClass()
# Do things here that are needed only once (like loging in)
def setUp(self) -> None:
# This is executed before every test
pass
def testOne(self):
# Write your tests here
pass
def testTwo(self):
# Write your tests here
pass
def tearDown(self) -> None:
# This is executed after every test
pass
if __name__ == '__main__':
# Load the tests from the suite class we created
test_cases = unittest.defaultTestLoader.loadTestsFromTestCase(TestLogin)
# If you want do add more
test_cases.addTests(TestSomethingElse)
# Run the actual tests
unittest.TextTestRunner().run(test_cases)

Discord Module never used?

I'm relatively confused here, and upon trying to research for an answer, I'm not seeming to find anything that makes any sense to me. I have created a discord bot with 5 cogs, and in each one I import discord, os, and from discord.ext import commands In various other cogs I import other modules such as random as the case may be, but those are the three common ones.
The problem is that in every module, import discord is grayed out (PyCharm IDE), suggesting that is never used. Despite this, my bot runs perfectly. I don't seem to be able to use things like the wait_for() command, I presume it is because it is in the discord module? Am I not setting things up correctly to use this?
I will post the initial startup module and a small snippet of another module, rather than list module. If you need more information, let me know.
initial startup:
import discord
import os
from discord.ext import commands
token = open("token.txt", "r").read()
client = commands.Bot(command_prefix = '!')
#client.command()
async def load(ctx, extension):
client.load_extension("cogs." + extension)
#client.command()
async def unload(ctx, extension):
client.unload_extension("cogs." + extension)
for filename in os.listdir("./cogs"):
if filename.endswith('.py'):
client.load_extension("cogs." + filename[:-3])
client.run(token)
another module:
import discord
from discord.ext import commands
import os
import json
from pathlib import Path
class Sheet(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
#commands.dm_only()
async def viewchar(self, ctx):
#Snipped code here to make it shorter.
pass
#viewchar.error
async def stats_error(self, ctx, error):
if isinstance(error, commands.PrivateMessageOnly):
await ctx.send("You're an idiot, now everyone knows. Why would you want to display your character sheet "
"in a public room? PM me with the command.")
else:
raise error
def setup(client):
client.add_cog(Sheet(client))
That just means that your code doesn't directly reference the discord module anywhere. You're getting everything through the commands module.
You can remove the import discord from your code without breaking anything, because the code that relies on it will still import and use it behind the scenes.

How to skip a test in pytest based on command-line option / fixture

I have a pytest suite containing multiple files that test web services. The tests can be run on different types, call them type A and type B and the user can specify which type the tests should be run for. While most tests are applicable for type A and B, some are not applicable for type B. I need to be able to skip certain tests when pytest is run with the --type=B flag.
Here is my conftest.py file where I setup a fixture based on type
import pytest
#Enable type argument
def pytest_addoption(parser):
parser.addoption("--type", action="store", default="A", help = "Specify a content type, allowed values: A, B")
#pytest.fixture(scope="session", autouse=True)
def type(request):
if request.node.get_closest_marker('skipb') and request.config.getoption('--type') == 'B':
pytest.skip('This test is not valid for type B so it was skipped')
print("Is type B")
return request.config.getoption("--type")
Then, before my test function to be skipped I am adding the marker as follows:
class TestService1(object):
#pytest.mark.skipb()
def test_status(self, getResponse):
assert_that(getResponse.ok, "HTTP Request OK").is_true()
printResponse(getResponse)
class TestService2(object):
#pytest.mark.skipb()
def test_status(self, getResponse):
assert_that(getResponse.ok, "HTTP Request OK").is_true()
printResponse(getResponse)
I am able to run pytest and don't get any interpreter errors however it doesn't skip my test. Here is the command I use to run the test:
pytest -s --type=B
Update: I need to clarify that my tests are spread across multiple classes. Updated my code example to make this more clear.
We use something very similar to run an "extended parameter set". For this, you can use the following code:
In conftest.py:
def pytest_addoption(parser):
parser.addoption(
"--extended-parameter-set",
action="store_true",
default=False,
help="Run an extended set of parameters.")
def pytest_collection_modifyitems(config, items):
extended_parameter_set = config.getoption("--extended-parameter-set")
skip_extended_parameters = pytest.mark.skip(
reason="This parameter combination is part of the extended parameter "
"set.")
for item in items:
if (not extended_parameter_set
and "extended_parameter_set" in item.keywords):
item.add_marker(skip_extended_parameters)
Now, you can simply mark full tests or only some parametrizations of a test with "extended_parameter_set" and it will only be run if pytest is invoked with the --extended-parameter-set option

Debugging test cases when they are combination of Robot framework and python selenium

Currently I'm using Eclipse with Nokia/Red plugin which allows me to write robot framework test suites. Support is Python 3.6 and Selenium for it.
My project is called "Automation" and Test suites are in .robot files.
Test suites have test cases which are called "Keywords".
Test Cases
Create New Vehicle
Create new vehicle with next ${registrationno} and ${description}
Navigate to data section
Those "Keywords" are imported from python library and look like:
#keyword("Create new vehicle with next ${registrationno} and ${description}")
def create_new_vehicle_Simple(self,registrationno, description):
headerPage = HeaderPage(TestCaseKeywords.driver)
sideBarPage = headerPage.selectDaten()
basicVehicleCreation = sideBarPage.createNewVehicle()
basicVehicleCreation.setKennzeichen(registrationno)
basicVehicleCreation.setBeschreibung(description)
TestCaseKeywords.carnumber = basicVehicleCreation.save()
The problem is that when I run test cases, in log I only get result of this whole python function, pass or failed. I can't see at which step it failed- is it at first or second step of this function.
Is there any plugin or other solution for this case to be able to see which exact python function pass or fail? (of course, workaround is to use in TC for every function a keyword but that is not what I prefer)
If you need to "step into" a python defined keyword you need to use python debugger together with RED.
This can be done with any python debugger,if you like to have everything in one application, PyDev can be used with RED.
Follow below help document, if you will face any problems leave a comment here.
RED Debug with PyDev
If you are wanting to know which statement in the python-based keyword failed, you simply need to have it throw an appropriate error. Robot won't do this for you, however. From a reporting standpoint, a python based keyword is a black box. You will have to explicitly add logging messages, and return useful errors.
For example, the call to sideBarPage.createNewVehicle() should throw an exception such as "unable to create new vehicle". Likewise, the call to basicVehicleCreation.setKennzeichen(registrationno) should raise an error like "failed to register the vehicle".
If you don't have control over those methods, you can do the error handling from within your keyword:
#keyword("Create new vehicle with next ${registrationno} and ${description}")
def create_new_vehicle_Simple(self,registrationno, description):
headerPage = HeaderPage(TestCaseKeywords.driver)
sideBarPage = headerPage.selectDaten()
try:
basicVehicleCreation = sideBarPage.createNewVehicle()
except:
raise Exception("unable to create new vehicle")
try:
basicVehicleCreation.setKennzeichen(registrationno)
except:
raise exception("unable to register new vehicle")
...

Rendering Plone SchemaAddForm without main_template?

I'm attempting to write a Plone add-on that requires an "Add new" form. So far, I've managed to get this working very nicely using plone.directives.form.SchemaAddForm. I have the '##create-snippet' view registered in configure.zcml, and it works perfectly when I view the page normally.
However, the ultimate goal of this project is to get this add form into a TinyMCE popup window. I've created a working TinyMCE plugin for another, irrelevant portion of the add-on, and gotten that working well. However, when I try to navigate to my "##create-snippets" view in a tinyMCE window, I get:
LocationError: (Products.Five.metaclass.DirectoryResource2 object at 0x107162fd0, 'main_template')
My understanding of this issue is that, essentially, the SchemaAddForm class (or one of it's super classes, to be exact) wraps the form with the main Plone main_template when it renders the form. Since TinyMCE windows are their own, isolated little worlds, the template isn't available, and, therefore, cannot be rendered....or something like that? Please correct me if I'm way off.
What I really want to know, is if it's possible to set things up so only the form itself will render, without using the main_template? I would REALLY like to be able to take advantage of the schema-based forms (and their built-in validation), but still be able to keep everything within a TinyMCE window.
I've toyed around with creating my own ViewPageTemplateFile() template, and getting the form to render (somehow?) within it, but frankly, I have no idea how....or if that's even possible.
Please feel free to ask for more information if there's something I've omitted. I'm kinda new to this type of Plone development.
The code generating the form:
from Products.Five.browser import BrowserView
from uwosh.snippets.snippet import SnippetManager
from plone.directives.form import SchemaAddForm
import zope.interface
from plone.autoform.form import AutoExtensibleForm
import zope.schema
from plone.directives import form
import z3c
from Products.statusmessages.interfaces import IStatusMessage
_ = zope.i18nmessageid.MessageFactory(u'uwosh.snippets')
class ISnippet(form.Schema):
title = zope.schema.TextLine(
title=u'Title',
description=u'The title to associate with the snippet.',
required=True)
description = zope.schema.TextLine(
title=u'Description',
description=u'A short explanation of the snippet.',
required=True)
body = zope.schema.Text(
title=u'Body',
description=u'The actual content to be rendered on the page.',
required=True)
class SnippetForm(SchemaAddForm):
schema = ISnippet
#z3c.form.button.buttonAndHandler(_('Save'), name='save')
def handleAdd(self, action):
data, errors = self.extractData()
if errors:
self.status = self.formErrorsMessage
return
obj = self.createAndAdd(data)
if obj is not None:
# mark only as finished if we get the new object
self._finishedAdd = True
IStatusMessage(self.request).addStatusMessage(_(u"Snippet saved"), "info")
#z3c.form.button.buttonAndHandler(_(u'Cancel'), name='cancel')
def handleCancel(self, action):
IStatusMessage(self.request).addStatusMessage(_(u"Add New Snippet operation cancelled"), "info")
self.request.response.redirect(self.nextURL())
def create(self, data):
sm = SnippetManager()
#TODO:
#Include support for different folders from this form.
snippet = sm.createSnippet(data['title'], None ,data)
return snippet
def add(self, object):
#Since, for now, snippets are based upon ATDocuments, their creation is fairly staight-forward.
#So, we don't really need separate Add/Create steps.
return
def nextURL(self):
return self.context.absolute_url() + '/##create-snippet'