i am using pytest with selenium for automation tests and POM. I have files with page objects which they inherit from base_page_objects, files with test code and conftest.py where they are all #pytest.fixtures. My question is, how to store for example 200 locators?
def current_email(self):
return self.driver.find_element(By.CSS_SELECTOR, "input[id='current-email']")
Right now i am using simple method for each locator but i'm guessing that isn't a proper way to store locators. How to store all locators?
you can create a list and append your locator there.
list=[]
def current_email(self):
for i in range(200):
variable=self.driver.find_element(By.CSS_SELECTOR, "input[id='current-email']")
list.append(variable)
print(list)
This will vary greatly depending on the application you are automating and your framework architecture, so it's up to you to decide what will be best.
That being said your current approach is not bad at all. By calling the method every time you want to interact with the element and not storing a reference you minimize the chance of getting StaleElementException.
If you want to have all locators in different files that is easily achievable since all you need is a tuple containing By and the locator itself
# locators.py
from selenium.webdriver.common.by import By
current_mail_locator = (By.CSS_SELECTOR, "input[id='current-email']")
Then you import locators into your POM files:
from locators import *
#property
def current_email(self):
return self.driver.find_element(*current_mail_locator)
Benefits of this approach is that all your locators will be organised neatly into files, so it makes them easier to fix when eventually they change.
It also makes sense to use the #property decorator for these methods that don't require arguments, so you can use them as if they were fields (without "()")
current_email.click()
Instead of:
current_email().click()
Related
Could you please help me with a test scenario?
I'm starting to automate a project using python and selenium organized in page objects.
I have separated file for the locators, separated files for different pages (so far, only 2 pages) and separated file for the test itself.
In the locators file, I write my selector, which contains a variable. I need it to be variable because later on I will have more than 10 selectors with the same Xpath, where only the text changes. So, this text I set it as a variable.
class AgreementPageLocators:
.........
MAIN_SEARCH_PK = (By.XPATH, f'//div[contains(text(), {variable})]')
In the "agreement" page, I define the action what should be done. Basically is to add something from a dropdown box. Marked in blue, is where I call the locator which contains the variable.
In my test file, I call the agreements method, where I define the variable:
# add agreement if it doesn't exist
if len(browser.find_elements(*AgreementPageLocators.AGREEMENT_CRIME)) == 0:
agreement_page = AgreementPage(browser)
agreement_page.agreements(variable='text1')
else:
pass
Obviously this doesn't work, as my test returns:
MAIN_SEARCH_PK = (By.XPATH, f'//div[contains(text(), {variable})]')
E NameError: name 'variable' is not defined
By putting the desired text instead of the variable in the selector, everything works fine. But I want to make it dynamically and not rewriting lots of times the same function just with different selectors.
Could someone help me please?
Try to define MAIN_SEARCH_PK as
MAIN_SEARCH_PK = (By.XPATH, '//div[contains(text(), "{}")]')
and
def agreements(self, variable):
...
pluscrime = browser.find_element(*AgreementPageLocators.MAIN_SEARCH_PK.format(variable))
P.S. Do not add your code as image!!!
I want to test my code that is based on the API created by someone else, but im not sure how should I do this.
I have created some function to save the json into file so I don't need to send requests each time I run test, but I don't know how to make it work in situation when the original (check) function takes an input arg (problem_report) which is an instance of some class provided by API and it has this
problem_report.get_correction(corr_link) method. I just wonder if this is a sign of bad written code by me, beacuse I can't write a test to this, or maybe I should rewrite this function in my tests file like I showed at the end of provided below code.
# I to want test this function
def check(problem_report):
corrections = {}
for corr_link, corr_id in problem_report.links.items():
if re.findall(pattern='detailCorrection', string=corr_link):
correction = problem_report.get_correction(corr_link)
corrections.update({corr_id: correction})
return corrections
# function serves to load json from file, normally it is downloaded by API from some page.
def load_pr(pr_id):
print('loading')
with open('{}{}_view_pr.json'.format(saved_prs_path, pr_id)) as view_pr:
view_pr = json.load(view_pr)
...
pr_info = {'view_pr': view_pr, ...}
return pr_info
# create an instance of class MyPR which takes json to __init__
#pytest.fixture
def setup_pr():
print('setup')
pr = load_pr('123')
my_pr = MyPR(pr['view_pr'])
return my_pr
# test function
def test_check(setup_pr):
pr = setup_pr
checked_pr = pr.check(setup_rft[1]['problem_report_pr'])
assert checker_pr
# rewritten check function in test file
#mock.patch('problem_report.get_correction', side_effect=get_corr)
def test_check(problem_report):
corrections = {}
for corr_link, corr_id in problem_report.links.items():
if re.findall(pattern='detailCorrection', string=corr_link):
correction = problem_report.get_correction(corr_link)
corrections.update({corr_id: correction})
return corrections
Im' not sure if I provided enough code and explanation to underastand the problem, but I hope so. I wish you could tell me if this is normal that some function are just hard to test, and if this is good practice to rewritte them separately so I can mock functions inside the tested function. I also was thinking that I could write new class with similar functionality but API is very large and it would be very long process.
I understand your question as follows: You have a function check that you consider hard to test because of its dependency on the problem_report. To make it better testable you have copied the code into the test file. You will test the copied code because you can modify this to be easier testable. And, you want to know if this approach makes sense.
The answer is no, this does not make sense. You are not testing the real function, but completely different code. Well, the code may not start being completely different, but in short time the copy and the original will deviate, and it will be a maintenance nightmare to ensure that the copy always resembles the original. Improving code for testability is a different story: You can make changes to the check function to improve its testability. But then, exactly the same resulting function should be used both in the test and the production code.
How to better test the function check then? First, are you sure that using the original problem_report objects really can not be sensibly used in your tests? (Here are some criteria that help you decide: What to mock for python test cases?). Now, lets assume that you come to the conclusion you can not sensibly use the original problem_report.
In that case, here the interface is simple enough to define a mocked problem_report. Keep in mind that Python uses duck typing, so you only have to create a class that has a links member which has an items() method. Plus, your mocked problem_report class needs a method get_correction(). Beyond that, your mock does not have to produce types that are similar to the types used by problem_report. The items() method can return simply a list of lists, like [["a",2],["xxxxdetailCorrectionxxxx",4]]. The same argument holds for get_correction, which could for example simply return its argument or a derived value, like, its negative.
For the above example (items() returning [["a",2],["xxxxdetailCorrectionxxxx",4]] and get_correction returning the negative of its argument) the expected result would be {4: -4}. No need to simulate real correction objects. And, you can create your mocked versions of problem_report without need to read data from files - the mocks can be setup completely from within the unit-testing code.
Try patching the problem_report symbol in the module. You should put your tests in a separate class.
#mock.patch('some.module.path.problem_report')
def test_check(problem_report):
problem_report.side_effect = get_corr
corrections = {}
for corr_link, corr_id in problem_report.links.items():
if re.findall(pattern='detailCorrection', string=corr_link):
correction = problem_report.get_correction(corr_link)
corrections.update({corr_id: correction})
return corrections
I'm trying to push a button on a soccer bookmaker's web page using Selenium's Chromedriver. My problem is that nothing happens when I call Selenium's driver.find_elements_by_class_name('c-events__item c-events__item_col').
Fixed:
I was trying to get the info from: a class names 'c-events__more c-events__more_bets js-showMoreBets'.
using find_by_class_name() cannot handle spaces as it will think its compound classes, instead I used csselector and it works like a charm now.
driver.find_elements_by_css_selector('.c-events__item.c-events__item_col')
It's telling you that you can't use multiple classes within find_elements_by_class_name. You can only use one class at a time. Since the method returns a selenium object, you could just chain the method three times, but the better thing to do is to just use the css selector method.
selenium.common.exceptions.InvalidSelectorException: Message: invalid selector: Compound class names not permitted
You should use a different method. If you want to select based on multiple classes, I recommend using the find_elements_by_css_selector method on the driver object. The dots before the class names make what is called a class selector. You can read more about selectors here: https://www.w3schools.com/cssref/css_selectors.asp
driver.find_elements_by_css_selector('.c-events__more .c-events__more_bets .js-showMoreBets')
Since you're hitting an anchor tag, you might want to add the a to the selector as well. E.g.
driver.find_elements_by_css_selector('.c-events__more .c-events__more_bets .js-showMoreBets a')
This is always my goto page for using python selenium. Here are all of the methods available for use -> http://selenium-python.readthedocs.io/locating-elements.html
using find_by_class_name() cannot handle spaces as it will think its compound classes, instead I used csselector and it works like a charm now. driver.find_elements_by_css_selector('.c-events__item.c-events__item_col')
I am writing a python plugin for custom HTML report for pytest test results. I want to store some arbitrary test information (i.o. some python objects...) inside tests, and then when making report I want to reuse this information in the report. So far I have only came with a bit of hackish solution.
I pass request object to my test and fill the request.node._report_sections part of it with my data.
This object is then passed to TestReport.sections attribute, which is available via hook pytest_runtest_logreport, from which finally I can generate HTML and then I remove all my objects from sections attribute.
In pseudopythoncode:
def test_answer(request):
a = MyObject("Wooo")
request.node._report_sections.append(("call","myobj",a))
assert False
and
def pytest_runtest_logreport(report):
if report.when=="call":
#generate html from report.sections content
#clean report.sections list from MyObject objects
#(Which by the way contains 2-tuples, i.e. ("myobj",a))
Is there a better pytest way to do this?
This way seems OK.
Improvements I can suggest:
Think about using a fixture to create the MyObject object. Then you can place the request.node._report_sections.append(("call","myobj",a)) inside the fixture, and make it invisible in the test. Like this:
#pytest.fixture
def a(request):
a_ = MyObject("Wooo")
request.node._report_sections.append(("call","myobj",a_))
return a_
def test_answer(a):
...
Another idea, which is suitable in case you have this object in all of your tests, is to implement one of the hooks pytest_pycollect_makeitem or pytest_pyfunc_call, and "plant" the object there in the first place.
Lets say I have a program that has a large number of configuration options. The user can specify them in a config file. My program can parse this config file, but how should it internally store and pass around the options?
In my case, the software is used to perform a scientific simulation. There are about 200 options most of which have sane defaults. Typically the user only has to specify a dozen or so. The difficulty I face is how to design my internal code. Many of the objects that need to be constructed depend on many configuration options. For example an object might need several paths (for where data will be stored), some options that need to be passed to algorithms that the object will call, and some options that are used directly by the object itself.
This leads to objects needing a very large number of arguments to be constructed. Additionally, as my codebase is under very active development, it is a big pain to go through the call stack and pass along a new configuration option all the way down to where it is needed.
One way to prevent that pain is to have a global configuration object that can be freely used anywhere in the code. I don't particularly like this approach as it leads to functions and classes that don't take any (or only one) argument and it isn't obvious to the reader what data the function/class deals with. It also prevents code reuse as all of the code depends on a giant config object.
Can anyone give me some advice about how a program like this should be structured?
Here is an example of what I mean for the configuration option passing style:
class A:
def __init__(self, opt_a, opt_b, ..., opt_z):
self.opt_a = opt_a
self.opt_b = opt_b
...
self.opt_z = opt_z
def foo(self, arg):
algo(arg, opt_a, opt_e)
Here is an example of the global config style:
class A:
def __init__(self, config):
self.config = config
def foo(self, arg):
algo(arg, config)
The examples are in Python but my question stands for any similar programming langauge.
matplotlib is a large package with many configuration options. It use a rcParams module to manage all the default parameters. rcParams save all the default parameters in a dict.
Every functions will get the options from keyword argurments:
for example:
def f(x,y,opt_a=None, opt_b=None):
if opt_a is None: opt_a = rcParams['group1.opt_a']
A few design patterns will help
Prototype
Factory and Abstract Factory
Use these two patterns with configuration objects. Each method will then take a configuration object and use what it needs. Also consider applying a logical grouping to config parameters and think about ways to reduce the number of inputs.
psuedo code
// Consider we can run three different kinds of Simulations. sim1, sim2, sim3
ConfigFactory configFactory = new ConfigFactory("/path/to/option/file");
....
Simulation1 sim1;
Simulation2 sim2;
Simulation3 sim3;
sim1.run( configFactory.ConfigForSim1() );
sim2.run( configFactory.ConfigForSim2() );
sim3.run( configFactory.ConfigForSim3() );
Inside of each factory method it might create a configuration from a prototype object (that has all of the "sane" defaults) and the option file becomes just the things that are different from default. This would be paired with clear documentation on what these defaults are and when a person (or other program) might want to change them.
** Edit: **
Also consider that each config returned by the factory is a subset of the overall config.
Pass around either the config parsing class, or write a class that wraps it and intelligently pulls out the requested options.
Python's standard library configparser exposes the sections and options of an INI style configuration file using the mapping protocol, and so you can retrieve your options directly from that as though it were a dictionary.
myconf = configparser.ConfigParser()
myconf.read('myconf.ini')
what_to_do = myconf['section']['option']
If you explicitly want to provide the options using the attribute notation, create a class that overrides __getattr__:
class MyConf:
def __init__(self, path):
self._parser = configparser.ConfigParser()
self._parser.read('myconf.ini')
def __getattr__(self, option):
return self._parser[{'what_to_do': 'section'}[option]][option]
myconf = MyConf()
what_to_do = myconf.what_to_do
Have a module load the params to its namespace, then import it and use wherever you want.
Also see related question here