Package: selene_wdm_4.0.1
Selene - User-oriented Web UI browser tests in Python (Selenide port)Main features:User-oriented API for Selenium Webdriver(code like speak common English)Ajax support(Smart implicit waiting and retry mechanism)PageObjects support(all elements are lazy-evaluated objects)Automatic driver management(no need to install and setup driver for quick local execution)Selene was inspired bySelenidefrom Java world.Tests with Selene can be built either in a simple straightforward "selenide" style or with PageObjects composed from Widgets i.e. reusable element components.VersionsMigration GuidePrerequisitesInstallationUsageQuick StartCore APIAutomatic Driver and Browser ManagementAdvanced APITutorialsExamplesContributionRelease WorkflowChangelogVersionsLatest recommended version to use is2.0.0b17+it's a completely new version of selene, with improved API and speedsupports Python3.7+bundled with Selenium4.1+it's incompatible with1.xcurrent master branch is pointed to 2.xyet in alpha/beta stage, refining API, improving "migratability" and testingmost active Selene users already upgraded to 2.0 alpha/beta
and have been using it in production during last 2 yearsthe only risk is API changes,
some commands are in progress of deprecation and renamingLatest version marked as stable is:1.0.2its sources and corresponding README version
can be found at1.xbranch.supports python2.7, 3.5, 3.6, 3.7THIS README DESCRIBES THE USAGE OF THE PRE-RELEASE version of Selene. For older docs look at1.xbranch.Migration guideFrom1.0.2to2.0.0b<LATEST>:upgrade to Python 3.7+update selene to2.0.0b<LATEST>find&replace thecollection.first()method from.first()to.firstensure all conditions liketext('foo')are used viabe.*orhave.*syntaxexample:find&replace all(text('foo'))to(have.text('foo'))(visible)to(be.visible)smarter find&replace (with some manual refactoring).should(x, timeout=y)to.with_(timeout=y).should(x).should_not(be.*)to.should(be.not_.*)or.should(be.*.not_).should_not(have.*)to.should(have.no.*)or.should(have.*.not_).should_each(condition)to.should(condition.each)and add corresponding imports:from selene import be, havefix another broken imports if availablerun tests, read deprecation warnings, and refactor to new style recommended in warning messagesPrerequisitesPython 3.7+Givenpyenvinstalled, installing needed version of Python is pretty simple:$ pyenv install 3.7.3
$ pyenv global 3.7.3
$ python -V
Python 3.7.3Installationvia poetry + pyenv (recommended)GIVENpoetryandpyenvinstalled ...ANDpoetry new my-tests-with-selene
cd my-tests-with-selene
pyenv local 3.7.3WHEN latest pre-release recommended version:poetry add selene --allow-prereleasesWHEN latest stable version:poetry add seleneTHENpoetry installvia pipLatest recommended pre-release alpha version:pip install selene --preLatest stable version:pip install selenefrom sourcesGIVEN webdriver and webdriver_manager are already installedTHENgit clone https://github.com/yashaka/selene.git
python setup.py installor using pip:pip install git+https://github.com/yashaka/selene.gitUsageQuick StartSimply...fromseleneimportbrowser,by,be,havebrowser.open('https://google.com/ncr')browser.element(by.name('q')).should(be.blank)\.type('selenium').press_enter()browser.all('#rso>div').should(have.size_greater_than(5))\.first.should(have.text('Selenium automates browsers'))OR with custom setupfromseleneimportbrowser,by,be,havebrowser.config.driver_name='firefox'browser.config.base_url='https://google.com'browser.config.timeout=2# browser.config.* = ...browser.open('/ncr')browser.element(by.name('q')).should(be.blank)\.type('selenium').press_enter()browser.all('#rso>div').should(have.size_greater_than(5))\.first.should(have.text('Selenium automates browsers'))OR more Selenide from java style:fromseleneimportbrowser,by,be,havefromselene.support.sharedimportconfigfromselene.support.shared.jquery_styleimports,ssconfig.browser_name='firefox'config.base_url='https://google.com'config.timeout=2# config.* = ...browser.open('/ncr')s(by.name('q')).should(be.blank)\.type('selenium').press_enter()ss('#rso>div').should(have.size_greater_than(5))\.first.should(have.text('Selenium automates browsers'))Core Api# Given:fromselenium.webdriverimportChrome# AND chromedriver executable available in $PATH# WHEN:fromseleneimportBrowser,Configbrowser=Browser(Config(driver=Chrome(),base_url='https://google.com',timeout=2,))# AND:browser.open('/ncr')# AND:# browser.element('//*[@name="q"]')).type('selenium').press_enter()# OR...# browser.element('[name=q]')).type('selenium').press_enter()# OR...fromseleneimportby# browser.element(by.name('q')).type('selenium').press_enter()# OR...for total readabilityquery=browser.element(by.name('q'))# actual search doesn't start on calling browser.element above,# i.e. the element is "lazy"... or in other words it serves as locator# Below, on calling actual first action,#     ⬇ the actual webelement is located first timequery.type('selenium').press_enter()#                      ⬆#                  and here it's located again, i.e. the element is "dynamic"# AND in case we need to filter collection of items#     by some condition like visibility:fromseleneimportberesults=browser.all('#rso>div').by(be.visible)# THEN we can assert some condition:fromseleneimporthave# results.should(have.size_greater_than(5))# results.first.should(have.text('Selenium automates browsers'))# OR...results.should(have.size_greater_than(5))\.first.should(have.text('Selenium automates browsers'))# FINALLY the browser can be quit:browser.quit()# but it's not mandatory, because by default Selenes kills all drivers on exit# that can be disabled by:browser.config.hold_driver_at_exit=TrueAutomatic Driver and Browser ManagementInstead of:fromselenium.webdriverimportChromefromseleneimportBrowser,Configbrowser=Browser(Config(driver=Chrome(),base_url='https://google.com',timeout=2))browser.open('/ncr')You can simply use the browser instance predefined for you inselenemodule:fromseleneimportbrowserbrowser.config.base_url='https://google.com'browser.config.timeout=2browser.open('/ncr')So you don't need to create you driver instance manually. It will be created for you automatically.Yet, if you need some special case, like working with remote driver, etc., you can still use shared browser object with additional configuration:fromseleniumimportwebdriverfromseleneimportbrowseroptions=webdriver.ChromeOptions()options.add_argument('--headless')options.add_argument('--no-sandbox')options.add_argument('--disable-gpu')options.add_argument('--disable-notifications')options.add_argument('--disable-extensions')options.add_argument('--disable-infobars')options.add_argument('--enable-automation')options.add_argument('--disable-dev-shm-usage')options.add_argument('--disable-setuid-sandbox')browser.config.driver_options=optionsbrowser.config.driver_remote_url='http://localhost:4444/wd/hub',browser.config.base_url='https://google.com'browser.config.timeout=2browser.open('/ncr')...But if you like to create the driver on your own, you can do it too:fromseleniumimportwebdriverfromseleneimportbrowseroptions=webdriver.ChromeOptions()options.add_argument('--headless')# ... other argumentsbrowser.config.driver=webdriver.Remote('http://localhost:4444/wd/hub',options=options)# Once you start to build and set the driver on your own,# probably you are going to fully manage it life cycle,# thus, consider disabling the automatic driver reset on browser.open# if driver was crashed or quit:browser.config._reset_not_alive_driver_on_get_url=False# And consider disabling the automatic driver quit on exit:browser.config.hold_driver_at_exit=True# Other common options will still be useful:browser.config.base_url='https://google.com'browser.config.timeout=2browser.open('/ncr')...# Finally, you can quit the driver manually:browser.quit()Advanced APISometimes you might need some extra things to reach your specific goals... Here go examples of Selene'scommand,query, custom conditions,.matching(condition)and.wait_until(condition)...fromseleneimportbrowser,have...#################################################### Maybe you need some advanced actions on elements,# e.g. for workaround something through js:fromseleneimportcommandbrowser.element('#not-in-view').perform(command.js.scroll_into_view)...#################################################### Probably you think that will need something like:fromseleneimportquery...defmy_int_from(text):returnint(text.split(' ')[0])product_text=browser.element('#price-label').get(query.text)# ... to assert something not standard:price=my_int_from(product_text)assertprice>100# But such version is very unstable in dynamic web world...# Usually it's...# either better to implement your custom condition:fromselene.core.conditionimportConditionfromselene.core.conditionsimportElementConditionfromselene.core.entityimportElementdefhave_in_text_the_int_number_more_than(number)->Condition[Element]:deffn(element:Element)->None:text=element.get(query.text)parsed_number=my_int_from(text)ifnotparsed_number>number:raiseAssertionError(f'actual text was:{text}'f'with parsed int number:{parsed_number}')returnElementCondition(f'has in text the int number more than:{number}',fn)browser.element('#price-label').should(have_in_text_the_int_number_more_than(100))'''# You even can create your own project_package/selene_extensions/have.py# with the following content:from selene.support.conditioins.have import *def int_number_more_than(number) -> Condition[Element]:def fn(element: Element) -> None:text = element.get(query.text)parsed_number = my_int_from(text)if not parsed_number > number:raise AssertionError(f'actual text was: {text}'f'with parsed int number: {parsed_number}')return ElementCondition(f'has in text the int number more than: {number}',fn)# And then in your test:from project_package.selene_extensions import havebrowser.element('#price-label').should(have.text('Price: ') \.should(have.int_number_more_than(100))# i.e. using it same style as in selene,# with also access to all original selene conditions'''# Such condition-based alternative to the original `assert price > 100` is less fragile,# because Python's `assert` does not have "implicit waiting",# while Selene's `should` command does have ;)# Furthermore, the good test is when you totally control your test data,# and the code like below:product=browser.element('#to-remember-for-future')product_text_before=product.get(query.text)price_before=my_int_from(product_text_before)...# some test stepsproduct_text_after=product.get(query.text)price_after=my_int_from(product_text_after)assertprice_after>price_before# – normally, should be refactored to something like:product=browser.element('#to-remember-for-future')product.should(have.text('100$'))...# some test stepsproduct.should(have.text('125$'))################################################ You might also think you need something like:fromseleneimportqueryifbrowser.element('#i-might-say-yes-or-no').get(query.text)=='yes':...# do something...# Or:fromseleneimportqueryifbrowser.all('.option').get(query.size)>=2:...# do something...# – maybe, one day, you really find a use case:)# But for above cases, probably easier would be:ifbrowser.element('#i-might-say-yes-or-no').wait_until(have.text('yes')):...# do something...ifbrowser.all('.i-will-appear').wait_until(have.size_greater_than_or_equal(2)):...# do something# Or, by using non-waiting versions, if "you are in a rush:)":ifbrowser.element('#i-might-say-yes-or-no').matching(have.text('yes')):...# do something...ifbrowser.all('.i-will-appear').matching(have.size_greater_than_or_equal(2)):...# do somethingTutorialsTBDMore ExamplesProject templateTBDContributionsee CONTRIBUTING.mdRelease Workflowsee Release workflowChangelogsee CHANGELOG.md