summaryrefslogtreecommitdiff
path: root/python-behaving.spec
diff options
context:
space:
mode:
Diffstat (limited to 'python-behaving.spec')
-rw-r--r--python-behaving.spec2062
1 files changed, 2062 insertions, 0 deletions
diff --git a/python-behaving.spec b/python-behaving.spec
new file mode 100644
index 0000000..58d4c61
--- /dev/null
+++ b/python-behaving.spec
@@ -0,0 +1,2062 @@
+%global _empty_manifest_terminate_build 0
+Name: python-behaving
+Version: 3.1.5
+Release: 1
+Summary: BDD Behavior-Driven-Development testing
+License: GPL
+URL: https://github.com/ggozad/behaving
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/37/58/3c2ded91ec19f6a36fdf22da02172ba962d2c5c7d83819f36c5ba1bf5607/behaving-3.1.5.tar.gz
+BuildArch: noarch
+
+Requires: python3-parse
+Requires: python3-behave
+Requires: python3-splinter
+Requires: python3-selenium
+
+%description
+# behaving
+
+[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ggozad/behaving/ci.yml)](https://github.com/ggozad/behaving/actions/workflows/ci.yml)
+[![PyPI](https://img.shields.io/pypi/v/behaving)](https://pypi.org/project/behaving/)
+[![Docker Image Version (latest by date)](https://img.shields.io/docker/v/behaving/behaving)](https://hub.docker.com/repository/docker/behaving/behaving)
+
+_behaving_ is a web application testing framework for
+Behavior-Driven-Development, based on
+[behave](http://pypi.python.org/pypi/behave) and
+[splinter](https://github.com/cobrateam/splinter).
+
+_behave_ is written in Python and is similar to
+[Cucumber](http://cucumber.io/).
+_behaving_ adds the step-libraries for multi-user web/email/sms/gcm
+interactions, and provides the Python _behaving_ namespace so that
+independent step-libraries can work together.
+
+Please refer to _behave_'s excellent
+[documentation](http://behave.readthedocs.io/en/latest/) for a guide on
+how to use it, how to write your custom steps and make it possible to
+extend _behaving_.
+
+## Hello world
+
+Starting to use _behaving_ is pretty easy. Inside some python module,
+add your _features_ consisting each of one or more scenarios. These
+features are Gherkin language files with an extension of `.feature`. In
+the same directory you should have a steps module which imports the
+_behaving_ steps as well as your own custom steps (more on that later in
+the setup\_ section) . Here's a basic example:
+
+```gherkin
+Feature: Text presence
+
+ Background:
+ Given a browser
+
+ Scenario: Search for BDD
+ When I visit "http://www.wikipedia.org/"
+ And I fill in "search" with "BDD"
+ And I press "go"
+ Then I should see "Behavior-driven development" within 5 seconds
+```
+
+## Email, SMS & GCM (Google Cloud Messaging)
+
+While the web is the focus of _behaving_, it also includes simple mocks
+for a mail, SMS and a GCM server. These come with a small collection of
+steps allowing you to do things like:
+
+```gherkin
+Feature: Email & SMS
+
+ Scenario: Click link in an email
+ Given a browser
+ When I send an email to "foo@bar.com" with subject "Hello" and body "Try out this website at http://google.com"
+ And I click the link in the email I received at "foo@bar.com"
+ Then the browser's URL should be "http://google.com/"
+
+ Scenario: Receive SMS with body
+ When I send an sms to "+4745690001" with body "Hello world"
+ Then I should receive an sms at "+4745690001" containing "world"
+
+ Scenario: Receive GCM Notification
+ When I send a gcm message "{"to":"deviceID", "data": {"message": "Foo Bar", "badge": 6}}"
+ Then I should receive a gcm notification at "deviceID" containing "{'data': {'message': 'Foo Bar'}}"
+```
+
+Typically, it will be your web application that sends
+email/sms/notifications and testing it comes down to configuring the
+application to send email/sms/notifications to the mock servers.
+
+## Personas & state
+
+A lot of web apps today rely on multi-user interactions. To help you
+with those interactions, _behaving_ uses the notion of _personas_. A
+persona within a test runs in its own instance of a browser and you can
+have more than one persona (and its browser instance) running
+concurrently. You switch among personas by calling
+
+```gherkin
+Given "PersonaName" as the persona
+```
+
+Personas are also typically implemented as simple dictionaries allowing
+them to carry state, save and reuse variables inside a scenario. When a
+persona is first invoked it is created as an empty dictionary. You can
+predefine personas though with set values.
+
+Let's take the familiar LOTR characters as our test users. On setting up
+the test environment (details later in the setup\_ section), we set up
+the characters basic variables we might be needing in the tests as such:
+
+```python
+PERSONAS = {
+ 'Frodo': dict(
+ fullname=u'Frodo Baggins',
+ email=u'frodo@shire.com',
+ password=u'frodopass',
+ mobile='+4745690001',
+ address: {
+ street: "The Shire",
+ zip: "4321"
+ }
+ ),
+ 'Gandalf': dict(
+ fullname=u'Gandalf the Grey',
+ email=u'gandalf@wizardry.com',
+ password=u'gandalfpass',
+ mobile='+4745690004',
+ address: {
+ street: "Rivendell street 1",
+ zip: "1234"
+ }
+ ),
+ ...
+}
+def before_scenario(context, scenario):
+ ...
+ context.personas = PERSONAS
+```
+
+Within a test and given a persona, you can now use `$var_name` to access
+a variable of a persona. You can also set new variables on personas. So
+the following,
+
+```gherkin
+Given "Gandalf" as the persona
+When I fill in "name" with "$fullname"
+And I fill in "street" with "$address.street"
+And I set "title" to the text of "document-title"
+And I fill in "delete" with "$title"
+And I set "address.country" to the text of "country"
+And I set "postaddress" to:
+"""
+$fullname
+$address.street, $address.zip, $address.country
+"""
+```
+
+would fill in the field with id `name` with `Gandalf the Grey`, `street`
+with `Rivendell street 1` set the variable `title` to the text of the
+element with id `document-title` and reuse the variable `title` to fill
+in the field with id `delete`. It would also store the value of the
+field with id "country" in address[`country`]. The `$var_name` pattern
+is also usable in the text received by steps that expect a body of text,
+which means that the `postaddress` persona variable will contain
+Gandalf's complete snail-mail postage address nicely formatted on
+multiple lines.
+
+## Hello Persona example
+
+Let us assume the following (coming from a real example) scenario.
+[Crypho](https://crypho.com), is an online messaging/sharing site that
+provides users with end-to-end encrypted real-time communications.
+_behaving_ was written to help test Crypho.
+
+In Crypho, teams collaborate in _spaces_. To invite somebody in a
+_space_ the invitee has to share a token with an invitor, so both can
+verify each other's identity.
+
+```gherkin
+Feature: Frodo invites Gandalf to The Shire space
+
+ Given state "the-shire"
+
+ Scenario: Frodo invites Gandalf to The Shire
+
+ Given "Gandalf" as the persona
+ When I log in
+```
+
+Before the scenarios start, the custom step `Given state "the-shire"`
+executes. This preloads the db with data, sets up the server etc. Then
+the scenario executes:
+
+First Gandalf logs in. The step `Given "Gandalf" as the persona`, fires
+up a browser that belongs to the persona Gandalf. The following step,
+`When I log in` is a custom step defined as follows:
+
+```python
+@when('I log in')
+def log_in(context):
+
+ assert context.persona
+ context.execute_steps(u"""
+ When I go to Home
+ Then I should see an element with id "email" within 2 seconds
+ When I fill in "email" with "$email"
+ And I press "send-sms"
+ Then I should see "We have sent you an SMS with a security code" within 2 seconds
+ And I should receive an sms at "$mobile"
+ And "token" should be enabled
+ When I parse the sms I received at "$mobile" and set "Your Crypho code is {token}"
+ And I fill in "token" with "$token"
+ And I fill in "password" with "$password"
+ And I press "login"
+ Then I should see "Crypho" within 5 seconds
+ """)
+```
+
+Observe above how the current persona (Gandalf) parses the sms it
+receives and saves it as "token". Later Gandalf reuses it to fill in the
+two-factor authentication field.
+
+Now that Gandalf is logged in, the test proceeds with Frodo. Frodo will
+log in, and invite Gandalf to a private space.
+
+```gherkin
+Given "Frodo" as the persona
+When I log in
+And I click the link with text that contains "My spaces"
+And I click the link with text that contains "The Shire"
+And I press "invite-members"
+ Then I should see "Invite members" within 1 seconds
+When I fill in "invitees" with "gandalf@wizardry.com"
+And I fill in "invitation-message" with "Come and join us!"
+And I press "send-invitations"
+ Then I should see "Your invitations have been sent" within 2 seconds
+```
+
+Once the invitations are sent we switch back to Gandalf's browser, who
+should have received a notification in his browser, as well as an email.
+He then proceeds to send an sms to Frodo with the token who completes
+the invitation.
+
+```gherkin
+Given "Gandalf" as the persona
+Then I should see "Your invitations have been updated" within 2 seconds
+And I should receive an email at "gandalf@wizardry.com" containing "Frodo Baggins has invited you to join a private workspace in Crypho"
+When I click the link with text that contains "Invitations"
+And I click the link with text that contains "Pending invitations"
+ Then I should see "Come and join us!"
+When I set "token" to the text of "invitation-token"
+And I send an sms to "45699900" with body "$token"
+
+Given "Frodo" as the persona
+ Then I should receive an sms at "45699900"
+When I set "FrodoToken" to the body of the sms I received at "45699900"
+And I click the link with text that contains "Invitations"
+And I click the link with text that contains "Enter authorization token"
+And I fill in "auth-token" with "$FrodoToken"
+And I press "Submit"
+ Then I should see "The invitation has been accepted." within 5 seconds
+ And I should see "Gandalf the Grey has joined the space, invited by Frodo Baggins" within 10 seconds
+```
+
+You can see the test in action on video
+[here](http://vimeo.com/63672466/).
+
+## Setting up a test environment
+
+Start by installing _behaving_ by using either `pip` or `easy_install`.
+This will also install dependencies and create the `behave` script with
+which you invoke your tests. If you prefer using buildout, clone the
+package itself from its repository, it contains already a buildout
+configuration.
+
+Typically you will be having a folder containing all your features and
+steps. For example a directory structure like the following:
+
+```
+features/
+features/mytest.feature
+features/myothertest.feature
+features/environment.py
+features/steps/
+features/steps/steps.py
+```
+
+In the steps directory you will need to import the _behaving_ steps you
+need. You can also define your own steps. So `steps.py` might look like:
+
+```python
+from behave import when
+from behaving.web.steps import *
+from behaving.sms.steps import *
+from behaving.mail.steps import *
+from behaving.notifications.gcm.steps import *
+from behaving.personas.steps import *
+
+@when('I go to home')
+def go_to_home(context):
+ context.browser.visit('https://web/')
+```
+
+In `environment.py` you specify settings as well the things that need to
+happen at various stages of testing, i.e. before and after everything, a
+feature run, or a scenario run. For convenience you can import and reuse
+`behaving.environment` which will perform default actions like closing
+all browsers after a scenario, clean the email folder etc.
+
+It is also possible to use `behaving.web.environment`,
+`behaving.mail.environment`, `behaving.sms.environment` and
+`behaving.personas.environment` on their own, if you don't have need for
+SMS for example.
+
+An example of an environment that does simply set some variables and
+then rely on default actions for the various stages, might look like the
+following:
+
+```python
+import os
+from behaving import environment as benv
+
+PERSONAS = {}
+
+def before_all(context):
+ import mypackage
+ context.attachment_dir = os.path.join(os.path.dirname(mypackage.__file__), 'tests/data')
+ context.sms_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/sms/')
+ context.gcm_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/gcm/')
+ context.mail_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/mail/')
+ benv.before_all(context)
+
+
+def after_all(context):
+ benv.after_all(context)
+
+
+def before_feature(context, feature):
+ benv.before_feature(context, feature)
+
+
+def after_feature(context, feature):
+ benv.after_feature(context, feature)
+
+
+def before_scenario(context, scenario):
+ benv.before_scenario(context, scenario)
+ context.personas = PERSONAS
+
+def after_scenario(context, scenario):
+ benv.after_scenario(context, scenario)
+```
+
+The following variables are supported and can be set to override
+defaults:
+
+- `screenshots_dir` (the path where screenshots will be saved. If it
+ is set, any failure in a scenario will result in a screenshot of the
+ browser at the time when the failure happened.)
+- `attachment_dir` (the path where file attachments can be found)
+- `sms_path` (the path to be used by `smsmock` to save sms. Defaults
+ to `current_dir/sms` )
+- `gcm_path` (the path to be used by `gcmmock` to save gcm
+ notifications. Defaults to `current_dir/gcm` )
+- `mail_path` (the path to be used by `mailmock` to save mail.
+ Defaults to `current_dir/mail` )
+- `default_browser`
+- `default_browser_size` (tuple (width, height), applied to each
+ browser as it's created)
+- `max_browser_attempts` (how many times to retry creating the browser
+ if it fails)
+- `remote_webdriver_url` (points to your selenium hub url or remote
+ webdriver. Defaults to `None`)
+- `browser_args` (a dict of additional keyword arguments used when
+ creating a browser)
+- `base_url` (the base url for a browser, allows you to use relative
+ paths)
+- `accept_ssl_certs` (setting to `True` will accept self-signed/invalid
+ certificates. Defaults to `None`)
+
+You can run the tests simply by issuing
+
+```sh
+./bin/behave ./features
+```
+
+For chrome and docker issues, the code below is useful
+
+```python
+from selenium.webdriver.chrome.options import Options
+chrome_options = Options()
+chrome_options.add_argument('--no-sandbox')
+context.browser_args = {
+ 'options': chrome_options
+}
+```
+
+## Mail, GCM and SMS mock servers
+
+When _behaving_ is installed, it creates three scripts to help you test
+mail, gcm and sms, `mailmock`, `gcmmock` and `smsmock` respectively. You
+can directly invoke them before running your tests, they all take a port
+as well as the directory to output data as parameters. For example,
+
+```sh
+./bin/smsmock -p 8081 -o ./var/sms
+./bin/gcmmock -p 8082 -o ./var/notifications/gcm
+./bin/mailmock -p 8083 -o ./var/mail [--no-stdout]
+```
+
+## `behaving.web` Supported matchers/steps
+
+- Browsers
+
+ - Given a browser [opens the default browser, i.e. Firefox]
+ - Given `brand` as the default browser [sets the default browser to be `brand`, this is the browser name when using the remote webdriver or Firefox, Chrome, Safari]
+ - Given the electron app "`app_path`" [for use with electron-based desktop apps]
+ - Given browser "`name`" [opens the browser named `name`]
+ - When I reload
+ - When I go back
+ - When I go forward
+ - When I resize the browser to `width`x`height`
+ - When I resize the viewport to `width`x`height`
+ - When I take a screenshot [will save a screenshot of the browser if `screenshots_dir` is set on the environment. Also, if `screenshots_dir` is set, all failing tests will result in a screenshot.]
+ - When I execute the script "`script`"
+ - When I set the cookie "`key`" to "`value`"
+ - When I delete the cookie "`key`"
+ - When I delete all cookies
+ - When I close the browser "`name`"
+
+- Frames
+
+ - When I switch to frame with css "`css`"
+ - When I switch back to the main page
+
+- Windows
+
+ - When I open a new window named "`name`" at "`url`"
+ - When I name the current window "`name`"
+ - When I switch to the window named "`name`"
+
+- URLs
+
+ - Given the base url "`url`" [sets the base url to `url`, alternatively set `context.base_url` directly in `environment.py`]
+ - When I visit "`url`"
+ - When I go to "`url`"
+ - When I parse the url path and set "`{expression}`"
+ - Then the browser's URL should be "`url`"
+ - Then the browser's URL should contain "`text`"
+ - Then the browser's URL should not contain "`text`"
+
+- Links
+
+ - When I click the link to "`url`"
+ - When I click the link to a url that contains "`url`"
+ - When I click the link with text "`text`"
+ - When I click the link with text that contains "`text`"
+
+- Text, element & class presence
+
+ - When I wait for `timeout` seconds
+ - When I show the element with id "`id`"
+ - When I hide the element with id "`id`"
+
+ - Text
+
+ - Then I should see "`text`"
+ - Then I should not see "`text`"
+ - Then I should see "`text`" within `timeout` seconds
+ - Then I should not see "`text`" within `timeout` seconds
+
+ - ID
+ - Then I should see an element with id "`id`"
+ - Then I should not see an element with id "`id`"
+ - Then I should see an element with id "`id`" within `timeout` seconds
+ - Then I should not see an element with id "`id`" within `timeout` seconds
+
+- CSS
+
+ - Existence
+ - Then I should see an element with the css selector "`selector`"
+ - Then I should not see an element with the css selector "`selector`"
+ - Then I should see an element with the css selector "`selector`" within `timeout` seconds
+ - Then I should not see an element with the css selector "`selector`" within `timeout` seconds
+ - Then I should see `n` elements with the css selector "`css`"
+ - Then I should see at least `n` elements with the css selector "`css`" within `timeout` seconds
+ - Visibility
+ - Then the element with the css selector "`css`" should be visible
+ - Then the element with the css selector "`css`" should be visible within `timeout` seconds
+ - Then the element with the css selector "`css`" should not be visible
+ - Then the element with the css selector "`css`" should be visible within `timeout` seconds
+ - Then {n:d} elements with the css selector "`css`" should be visible
+ - Then {n:d} elements with the css selector "`css`" should be visible within `timeout` seconds
+ - Then at least {n:d} elements with the css selector "`css`" should be visible
+ - Then at least {n:d} elements with the css selector "`css`" should be visible within `timeout` seconds
+ - Existence of a class on an element
+ - Then the element with xpath "`xpath`" should have the class "`cls`"
+ - Then the element with xpath "`xpath`" should not have the class "`cls`"
+ - Then the element with xpath "`xpath`" should have the class "`cls`" within `timeout` seconds
+ - Then the element with xpath "`xpath`" should not have the class "`cls`" within `timeout` seconds
+ - Then "`name`" should have the class "`cls`"
+ - Then "`name`" should not have the class "`cls`"
+ - Then "`name`" should have the class "`cls`" within `timeout` seconds
+ - Then "`name`" should not have the class "`cls`" within `timeout:d` seconds
+ - XPath
+ - Then I should see an element with xpath "`xpath`"
+ - Then I should not see an element with xpath "`xpath`"
+ - Then I should see an element with xpath "`xpath`" within `timeout` seconds
+ - Then I should not see an element with xpath "`xpath`" within `timeout` seconds
+
+- Forms
+
+ - When I fill in "`name|id`" with "`value`"
+ - When I clear field "`name|id`"
+ - When I type "`value`" to "`name|id`" [same as fill, but happens slowly triggering keyboard events]
+ - When I choose "`value`" from "`name`"
+ - When I check "`name|id`"
+ - When I uncheck "`name|id`"
+ - When I toggle "`name|id`"
+ - When I select "`value`" from "`name`""
+ - When I select by text "`text`" from "`name`""
+ - When I press "`name|id|text|innerText`"
+ - When I press the element with xpath "`xpath`"
+ - When I attach the file "`path`" to "`name`"
+ - When I set the innner HTML of the element with id "`id`" to "`contents`" [Sets html on a `contenteditable` element with id `id` to `contents`]
+ - When I set the innner HTML of the element with class "`class`" to "`contents`"
+ - When I set the innner HTML of the element with class "`class`" to "`contents`"
+ - When I send "`KEY`" to "`name`"
+ - When I focus on "`name`"
+ - Then field "`name`" should have the value "`value`"
+ - Then field "`name`" should have the value "`value`" within `timeout` seconds
+ - Then the selection "`name`" should have the options "`valueA, valueB`" selected
+ - Then "`name`" should be enabled
+ - Then "`name`" should be disabled
+ - Then "`name`" should not be enabled
+ - Then "`name`" should be valid
+ - Then "`name`" should be invalid
+ - Then "`name`" should not be valid
+ - Then "`name`" should be required
+ - Then "`name`" should not be required
+
+- HTML tables
+
+ - Then the table with id "`id`" should be
+ | header1 | header2 | ... | header(m) |
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+ ...
+ | celln0 | celln1 | ... | cellnm |
+
+ - Then the table with xpath "`xpath`" should be
+ | header1 | header2 | ... | header(m) |
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+ ...
+ | celln0 | celln1 | ... | cellnm |
+
+ - Then the table with id "`id`" should contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with xpath "`xpath`" should contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with id "`id`" should not contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with xpath "`xpath`" should not contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then row `row_no` in the table with id "`id`" should be
+ | cell00 | cell01 | ... | cell0m |
+
+ - Then row `row_no` in the table with xpath "`xpath`" should be
+ | cell00 | cell01 | ... | cell0m |
+
+ - Then the value of the cell in row `row_no`, column `col_no` in the table with id "`id`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column `col_no` in the table with xpath "`xpath`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column "`col_header`" in the table with id "`id`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column "`col_header`" in the table with xpath "`xpath`" should be "`value`"
+
+- Alerts & prompts
+
+ - When I enter "`text`" to the alert - When I accept the alert - When I dismiss the alert - Then I should see an alert - Then I should see an alert within `timeout` seconds - Then I should see an alert containing "`text`" - Then I should see an alert containing "`text`" within `timeout` seconds
+
+- Mouse
+
+ - When I mouse over the element with xpath "`xpath`"
+ - When I mouse out of the element with xpath "`xpath`"
+
+- Downloads
+
+ - Then the file "`filename`" with contents "`text`" should have been downloaded within `timeout` seconds
+ - Then the file "`filename`" should have been downloaded within `timeout` seconds
+
+- Persona interaction & variables
+
+ - When I set "`key`" to the text of "`id|name`"
+ - When I set "`key`" to the attribute "`attr`" of the element with xpath "`xpath`"
+ - When I evaluate the script "`script`" and assign the result to "`key`"
+
+## `behaving.mail` Supported matchers/steps
+
+- When I click the link in the email I received at "`address`"
+- When I parse the email I received at "`address`" and set "`expression`"
+- When I clear the email messages
+- Then I should receive an email at "`address`"
+- Then I should receive an email at "`address`" with subject "`subject`"
+- Then I should receive an email at "`address`" containing "`text`"
+- Then I should receive an email at "`address`" with attachment "`filename`"
+- Then I should not have received any emails at "`address`"
+
+## `behaving.sms` Supported matchers/steps
+
+- When I set "`key`" to the body of the sms I received at "`number`"
+- When I parse the sms I received at "`number`" and set "`expression`"
+- Then I should receive an sms at "`number`"
+- Then I should receive an sms at "`number`" containing "`text`"
+
+## `behaving.notifications.gcm` Supported matchers/steps
+
+- When I send a gcm message "{"to":"deviceID", "data": {"message":"Foo Bar", "badge": 6}}"
+- Then I should receive a gcm notification at "deviceID" containing "{'data': {'message': 'Foo Bar'}}"
+- Then I should have received any gcm notifications at "deviceID"
+
+## `behaving.personas` Supported matchers/steps
+
+- Given "`name`" as the persona
+- When I set "`key`" to "`value`"
+- When I set "`key`" to:
+ """ `some longer body of text`
+ `usually multiline`
+ """
+- When I clone persona "`source`" to "`target`"
+- Then "`key`" is set to "`value`"
+
+## Debugging
+
+- When I pause the tests
+
+## Docker integration
+
+A `Dockerfile` as well as a complete setup using `docker-compose` are provided to help you create selenium grid configurations that run your tests. In addition dev container configuration is included if VSCode is your thing.
+
+In addition we provide pre-build images on docker hub for the `linux/amd64` and `linux/arm64` platforms. Use
+
+```bash
+docker pull behaving/behaving:latest
+```
+
+to pull the image.
+
+## Running behaving tests
+
+You can run all behaving tests as follows:
+
+Start docker compose:
+
+```
+docker-compose up
+```
+
+Open a shell in the behaving container:
+
+```
+docker-compose exec behaving bash
+```
+
+Run behaving tests:
+
+```
+behave tests/features
+```
+
+
+%package -n python3-behaving
+Summary: BDD Behavior-Driven-Development testing
+Provides: python-behaving
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-behaving
+# behaving
+
+[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ggozad/behaving/ci.yml)](https://github.com/ggozad/behaving/actions/workflows/ci.yml)
+[![PyPI](https://img.shields.io/pypi/v/behaving)](https://pypi.org/project/behaving/)
+[![Docker Image Version (latest by date)](https://img.shields.io/docker/v/behaving/behaving)](https://hub.docker.com/repository/docker/behaving/behaving)
+
+_behaving_ is a web application testing framework for
+Behavior-Driven-Development, based on
+[behave](http://pypi.python.org/pypi/behave) and
+[splinter](https://github.com/cobrateam/splinter).
+
+_behave_ is written in Python and is similar to
+[Cucumber](http://cucumber.io/).
+_behaving_ adds the step-libraries for multi-user web/email/sms/gcm
+interactions, and provides the Python _behaving_ namespace so that
+independent step-libraries can work together.
+
+Please refer to _behave_'s excellent
+[documentation](http://behave.readthedocs.io/en/latest/) for a guide on
+how to use it, how to write your custom steps and make it possible to
+extend _behaving_.
+
+## Hello world
+
+Starting to use _behaving_ is pretty easy. Inside some python module,
+add your _features_ consisting each of one or more scenarios. These
+features are Gherkin language files with an extension of `.feature`. In
+the same directory you should have a steps module which imports the
+_behaving_ steps as well as your own custom steps (more on that later in
+the setup\_ section) . Here's a basic example:
+
+```gherkin
+Feature: Text presence
+
+ Background:
+ Given a browser
+
+ Scenario: Search for BDD
+ When I visit "http://www.wikipedia.org/"
+ And I fill in "search" with "BDD"
+ And I press "go"
+ Then I should see "Behavior-driven development" within 5 seconds
+```
+
+## Email, SMS & GCM (Google Cloud Messaging)
+
+While the web is the focus of _behaving_, it also includes simple mocks
+for a mail, SMS and a GCM server. These come with a small collection of
+steps allowing you to do things like:
+
+```gherkin
+Feature: Email & SMS
+
+ Scenario: Click link in an email
+ Given a browser
+ When I send an email to "foo@bar.com" with subject "Hello" and body "Try out this website at http://google.com"
+ And I click the link in the email I received at "foo@bar.com"
+ Then the browser's URL should be "http://google.com/"
+
+ Scenario: Receive SMS with body
+ When I send an sms to "+4745690001" with body "Hello world"
+ Then I should receive an sms at "+4745690001" containing "world"
+
+ Scenario: Receive GCM Notification
+ When I send a gcm message "{"to":"deviceID", "data": {"message": "Foo Bar", "badge": 6}}"
+ Then I should receive a gcm notification at "deviceID" containing "{'data': {'message': 'Foo Bar'}}"
+```
+
+Typically, it will be your web application that sends
+email/sms/notifications and testing it comes down to configuring the
+application to send email/sms/notifications to the mock servers.
+
+## Personas & state
+
+A lot of web apps today rely on multi-user interactions. To help you
+with those interactions, _behaving_ uses the notion of _personas_. A
+persona within a test runs in its own instance of a browser and you can
+have more than one persona (and its browser instance) running
+concurrently. You switch among personas by calling
+
+```gherkin
+Given "PersonaName" as the persona
+```
+
+Personas are also typically implemented as simple dictionaries allowing
+them to carry state, save and reuse variables inside a scenario. When a
+persona is first invoked it is created as an empty dictionary. You can
+predefine personas though with set values.
+
+Let's take the familiar LOTR characters as our test users. On setting up
+the test environment (details later in the setup\_ section), we set up
+the characters basic variables we might be needing in the tests as such:
+
+```python
+PERSONAS = {
+ 'Frodo': dict(
+ fullname=u'Frodo Baggins',
+ email=u'frodo@shire.com',
+ password=u'frodopass',
+ mobile='+4745690001',
+ address: {
+ street: "The Shire",
+ zip: "4321"
+ }
+ ),
+ 'Gandalf': dict(
+ fullname=u'Gandalf the Grey',
+ email=u'gandalf@wizardry.com',
+ password=u'gandalfpass',
+ mobile='+4745690004',
+ address: {
+ street: "Rivendell street 1",
+ zip: "1234"
+ }
+ ),
+ ...
+}
+def before_scenario(context, scenario):
+ ...
+ context.personas = PERSONAS
+```
+
+Within a test and given a persona, you can now use `$var_name` to access
+a variable of a persona. You can also set new variables on personas. So
+the following,
+
+```gherkin
+Given "Gandalf" as the persona
+When I fill in "name" with "$fullname"
+And I fill in "street" with "$address.street"
+And I set "title" to the text of "document-title"
+And I fill in "delete" with "$title"
+And I set "address.country" to the text of "country"
+And I set "postaddress" to:
+"""
+$fullname
+$address.street, $address.zip, $address.country
+"""
+```
+
+would fill in the field with id `name` with `Gandalf the Grey`, `street`
+with `Rivendell street 1` set the variable `title` to the text of the
+element with id `document-title` and reuse the variable `title` to fill
+in the field with id `delete`. It would also store the value of the
+field with id "country" in address[`country`]. The `$var_name` pattern
+is also usable in the text received by steps that expect a body of text,
+which means that the `postaddress` persona variable will contain
+Gandalf's complete snail-mail postage address nicely formatted on
+multiple lines.
+
+## Hello Persona example
+
+Let us assume the following (coming from a real example) scenario.
+[Crypho](https://crypho.com), is an online messaging/sharing site that
+provides users with end-to-end encrypted real-time communications.
+_behaving_ was written to help test Crypho.
+
+In Crypho, teams collaborate in _spaces_. To invite somebody in a
+_space_ the invitee has to share a token with an invitor, so both can
+verify each other's identity.
+
+```gherkin
+Feature: Frodo invites Gandalf to The Shire space
+
+ Given state "the-shire"
+
+ Scenario: Frodo invites Gandalf to The Shire
+
+ Given "Gandalf" as the persona
+ When I log in
+```
+
+Before the scenarios start, the custom step `Given state "the-shire"`
+executes. This preloads the db with data, sets up the server etc. Then
+the scenario executes:
+
+First Gandalf logs in. The step `Given "Gandalf" as the persona`, fires
+up a browser that belongs to the persona Gandalf. The following step,
+`When I log in` is a custom step defined as follows:
+
+```python
+@when('I log in')
+def log_in(context):
+
+ assert context.persona
+ context.execute_steps(u"""
+ When I go to Home
+ Then I should see an element with id "email" within 2 seconds
+ When I fill in "email" with "$email"
+ And I press "send-sms"
+ Then I should see "We have sent you an SMS with a security code" within 2 seconds
+ And I should receive an sms at "$mobile"
+ And "token" should be enabled
+ When I parse the sms I received at "$mobile" and set "Your Crypho code is {token}"
+ And I fill in "token" with "$token"
+ And I fill in "password" with "$password"
+ And I press "login"
+ Then I should see "Crypho" within 5 seconds
+ """)
+```
+
+Observe above how the current persona (Gandalf) parses the sms it
+receives and saves it as "token". Later Gandalf reuses it to fill in the
+two-factor authentication field.
+
+Now that Gandalf is logged in, the test proceeds with Frodo. Frodo will
+log in, and invite Gandalf to a private space.
+
+```gherkin
+Given "Frodo" as the persona
+When I log in
+And I click the link with text that contains "My spaces"
+And I click the link with text that contains "The Shire"
+And I press "invite-members"
+ Then I should see "Invite members" within 1 seconds
+When I fill in "invitees" with "gandalf@wizardry.com"
+And I fill in "invitation-message" with "Come and join us!"
+And I press "send-invitations"
+ Then I should see "Your invitations have been sent" within 2 seconds
+```
+
+Once the invitations are sent we switch back to Gandalf's browser, who
+should have received a notification in his browser, as well as an email.
+He then proceeds to send an sms to Frodo with the token who completes
+the invitation.
+
+```gherkin
+Given "Gandalf" as the persona
+Then I should see "Your invitations have been updated" within 2 seconds
+And I should receive an email at "gandalf@wizardry.com" containing "Frodo Baggins has invited you to join a private workspace in Crypho"
+When I click the link with text that contains "Invitations"
+And I click the link with text that contains "Pending invitations"
+ Then I should see "Come and join us!"
+When I set "token" to the text of "invitation-token"
+And I send an sms to "45699900" with body "$token"
+
+Given "Frodo" as the persona
+ Then I should receive an sms at "45699900"
+When I set "FrodoToken" to the body of the sms I received at "45699900"
+And I click the link with text that contains "Invitations"
+And I click the link with text that contains "Enter authorization token"
+And I fill in "auth-token" with "$FrodoToken"
+And I press "Submit"
+ Then I should see "The invitation has been accepted." within 5 seconds
+ And I should see "Gandalf the Grey has joined the space, invited by Frodo Baggins" within 10 seconds
+```
+
+You can see the test in action on video
+[here](http://vimeo.com/63672466/).
+
+## Setting up a test environment
+
+Start by installing _behaving_ by using either `pip` or `easy_install`.
+This will also install dependencies and create the `behave` script with
+which you invoke your tests. If you prefer using buildout, clone the
+package itself from its repository, it contains already a buildout
+configuration.
+
+Typically you will be having a folder containing all your features and
+steps. For example a directory structure like the following:
+
+```
+features/
+features/mytest.feature
+features/myothertest.feature
+features/environment.py
+features/steps/
+features/steps/steps.py
+```
+
+In the steps directory you will need to import the _behaving_ steps you
+need. You can also define your own steps. So `steps.py` might look like:
+
+```python
+from behave import when
+from behaving.web.steps import *
+from behaving.sms.steps import *
+from behaving.mail.steps import *
+from behaving.notifications.gcm.steps import *
+from behaving.personas.steps import *
+
+@when('I go to home')
+def go_to_home(context):
+ context.browser.visit('https://web/')
+```
+
+In `environment.py` you specify settings as well the things that need to
+happen at various stages of testing, i.e. before and after everything, a
+feature run, or a scenario run. For convenience you can import and reuse
+`behaving.environment` which will perform default actions like closing
+all browsers after a scenario, clean the email folder etc.
+
+It is also possible to use `behaving.web.environment`,
+`behaving.mail.environment`, `behaving.sms.environment` and
+`behaving.personas.environment` on their own, if you don't have need for
+SMS for example.
+
+An example of an environment that does simply set some variables and
+then rely on default actions for the various stages, might look like the
+following:
+
+```python
+import os
+from behaving import environment as benv
+
+PERSONAS = {}
+
+def before_all(context):
+ import mypackage
+ context.attachment_dir = os.path.join(os.path.dirname(mypackage.__file__), 'tests/data')
+ context.sms_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/sms/')
+ context.gcm_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/gcm/')
+ context.mail_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/mail/')
+ benv.before_all(context)
+
+
+def after_all(context):
+ benv.after_all(context)
+
+
+def before_feature(context, feature):
+ benv.before_feature(context, feature)
+
+
+def after_feature(context, feature):
+ benv.after_feature(context, feature)
+
+
+def before_scenario(context, scenario):
+ benv.before_scenario(context, scenario)
+ context.personas = PERSONAS
+
+def after_scenario(context, scenario):
+ benv.after_scenario(context, scenario)
+```
+
+The following variables are supported and can be set to override
+defaults:
+
+- `screenshots_dir` (the path where screenshots will be saved. If it
+ is set, any failure in a scenario will result in a screenshot of the
+ browser at the time when the failure happened.)
+- `attachment_dir` (the path where file attachments can be found)
+- `sms_path` (the path to be used by `smsmock` to save sms. Defaults
+ to `current_dir/sms` )
+- `gcm_path` (the path to be used by `gcmmock` to save gcm
+ notifications. Defaults to `current_dir/gcm` )
+- `mail_path` (the path to be used by `mailmock` to save mail.
+ Defaults to `current_dir/mail` )
+- `default_browser`
+- `default_browser_size` (tuple (width, height), applied to each
+ browser as it's created)
+- `max_browser_attempts` (how many times to retry creating the browser
+ if it fails)
+- `remote_webdriver_url` (points to your selenium hub url or remote
+ webdriver. Defaults to `None`)
+- `browser_args` (a dict of additional keyword arguments used when
+ creating a browser)
+- `base_url` (the base url for a browser, allows you to use relative
+ paths)
+- `accept_ssl_certs` (setting to `True` will accept self-signed/invalid
+ certificates. Defaults to `None`)
+
+You can run the tests simply by issuing
+
+```sh
+./bin/behave ./features
+```
+
+For chrome and docker issues, the code below is useful
+
+```python
+from selenium.webdriver.chrome.options import Options
+chrome_options = Options()
+chrome_options.add_argument('--no-sandbox')
+context.browser_args = {
+ 'options': chrome_options
+}
+```
+
+## Mail, GCM and SMS mock servers
+
+When _behaving_ is installed, it creates three scripts to help you test
+mail, gcm and sms, `mailmock`, `gcmmock` and `smsmock` respectively. You
+can directly invoke them before running your tests, they all take a port
+as well as the directory to output data as parameters. For example,
+
+```sh
+./bin/smsmock -p 8081 -o ./var/sms
+./bin/gcmmock -p 8082 -o ./var/notifications/gcm
+./bin/mailmock -p 8083 -o ./var/mail [--no-stdout]
+```
+
+## `behaving.web` Supported matchers/steps
+
+- Browsers
+
+ - Given a browser [opens the default browser, i.e. Firefox]
+ - Given `brand` as the default browser [sets the default browser to be `brand`, this is the browser name when using the remote webdriver or Firefox, Chrome, Safari]
+ - Given the electron app "`app_path`" [for use with electron-based desktop apps]
+ - Given browser "`name`" [opens the browser named `name`]
+ - When I reload
+ - When I go back
+ - When I go forward
+ - When I resize the browser to `width`x`height`
+ - When I resize the viewport to `width`x`height`
+ - When I take a screenshot [will save a screenshot of the browser if `screenshots_dir` is set on the environment. Also, if `screenshots_dir` is set, all failing tests will result in a screenshot.]
+ - When I execute the script "`script`"
+ - When I set the cookie "`key`" to "`value`"
+ - When I delete the cookie "`key`"
+ - When I delete all cookies
+ - When I close the browser "`name`"
+
+- Frames
+
+ - When I switch to frame with css "`css`"
+ - When I switch back to the main page
+
+- Windows
+
+ - When I open a new window named "`name`" at "`url`"
+ - When I name the current window "`name`"
+ - When I switch to the window named "`name`"
+
+- URLs
+
+ - Given the base url "`url`" [sets the base url to `url`, alternatively set `context.base_url` directly in `environment.py`]
+ - When I visit "`url`"
+ - When I go to "`url`"
+ - When I parse the url path and set "`{expression}`"
+ - Then the browser's URL should be "`url`"
+ - Then the browser's URL should contain "`text`"
+ - Then the browser's URL should not contain "`text`"
+
+- Links
+
+ - When I click the link to "`url`"
+ - When I click the link to a url that contains "`url`"
+ - When I click the link with text "`text`"
+ - When I click the link with text that contains "`text`"
+
+- Text, element & class presence
+
+ - When I wait for `timeout` seconds
+ - When I show the element with id "`id`"
+ - When I hide the element with id "`id`"
+
+ - Text
+
+ - Then I should see "`text`"
+ - Then I should not see "`text`"
+ - Then I should see "`text`" within `timeout` seconds
+ - Then I should not see "`text`" within `timeout` seconds
+
+ - ID
+ - Then I should see an element with id "`id`"
+ - Then I should not see an element with id "`id`"
+ - Then I should see an element with id "`id`" within `timeout` seconds
+ - Then I should not see an element with id "`id`" within `timeout` seconds
+
+- CSS
+
+ - Existence
+ - Then I should see an element with the css selector "`selector`"
+ - Then I should not see an element with the css selector "`selector`"
+ - Then I should see an element with the css selector "`selector`" within `timeout` seconds
+ - Then I should not see an element with the css selector "`selector`" within `timeout` seconds
+ - Then I should see `n` elements with the css selector "`css`"
+ - Then I should see at least `n` elements with the css selector "`css`" within `timeout` seconds
+ - Visibility
+ - Then the element with the css selector "`css`" should be visible
+ - Then the element with the css selector "`css`" should be visible within `timeout` seconds
+ - Then the element with the css selector "`css`" should not be visible
+ - Then the element with the css selector "`css`" should be visible within `timeout` seconds
+ - Then {n:d} elements with the css selector "`css`" should be visible
+ - Then {n:d} elements with the css selector "`css`" should be visible within `timeout` seconds
+ - Then at least {n:d} elements with the css selector "`css`" should be visible
+ - Then at least {n:d} elements with the css selector "`css`" should be visible within `timeout` seconds
+ - Existence of a class on an element
+ - Then the element with xpath "`xpath`" should have the class "`cls`"
+ - Then the element with xpath "`xpath`" should not have the class "`cls`"
+ - Then the element with xpath "`xpath`" should have the class "`cls`" within `timeout` seconds
+ - Then the element with xpath "`xpath`" should not have the class "`cls`" within `timeout` seconds
+ - Then "`name`" should have the class "`cls`"
+ - Then "`name`" should not have the class "`cls`"
+ - Then "`name`" should have the class "`cls`" within `timeout` seconds
+ - Then "`name`" should not have the class "`cls`" within `timeout:d` seconds
+ - XPath
+ - Then I should see an element with xpath "`xpath`"
+ - Then I should not see an element with xpath "`xpath`"
+ - Then I should see an element with xpath "`xpath`" within `timeout` seconds
+ - Then I should not see an element with xpath "`xpath`" within `timeout` seconds
+
+- Forms
+
+ - When I fill in "`name|id`" with "`value`"
+ - When I clear field "`name|id`"
+ - When I type "`value`" to "`name|id`" [same as fill, but happens slowly triggering keyboard events]
+ - When I choose "`value`" from "`name`"
+ - When I check "`name|id`"
+ - When I uncheck "`name|id`"
+ - When I toggle "`name|id`"
+ - When I select "`value`" from "`name`""
+ - When I select by text "`text`" from "`name`""
+ - When I press "`name|id|text|innerText`"
+ - When I press the element with xpath "`xpath`"
+ - When I attach the file "`path`" to "`name`"
+ - When I set the innner HTML of the element with id "`id`" to "`contents`" [Sets html on a `contenteditable` element with id `id` to `contents`]
+ - When I set the innner HTML of the element with class "`class`" to "`contents`"
+ - When I set the innner HTML of the element with class "`class`" to "`contents`"
+ - When I send "`KEY`" to "`name`"
+ - When I focus on "`name`"
+ - Then field "`name`" should have the value "`value`"
+ - Then field "`name`" should have the value "`value`" within `timeout` seconds
+ - Then the selection "`name`" should have the options "`valueA, valueB`" selected
+ - Then "`name`" should be enabled
+ - Then "`name`" should be disabled
+ - Then "`name`" should not be enabled
+ - Then "`name`" should be valid
+ - Then "`name`" should be invalid
+ - Then "`name`" should not be valid
+ - Then "`name`" should be required
+ - Then "`name`" should not be required
+
+- HTML tables
+
+ - Then the table with id "`id`" should be
+ | header1 | header2 | ... | header(m) |
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+ ...
+ | celln0 | celln1 | ... | cellnm |
+
+ - Then the table with xpath "`xpath`" should be
+ | header1 | header2 | ... | header(m) |
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+ ...
+ | celln0 | celln1 | ... | cellnm |
+
+ - Then the table with id "`id`" should contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with xpath "`xpath`" should contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with id "`id`" should not contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with xpath "`xpath`" should not contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then row `row_no` in the table with id "`id`" should be
+ | cell00 | cell01 | ... | cell0m |
+
+ - Then row `row_no` in the table with xpath "`xpath`" should be
+ | cell00 | cell01 | ... | cell0m |
+
+ - Then the value of the cell in row `row_no`, column `col_no` in the table with id "`id`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column `col_no` in the table with xpath "`xpath`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column "`col_header`" in the table with id "`id`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column "`col_header`" in the table with xpath "`xpath`" should be "`value`"
+
+- Alerts & prompts
+
+ - When I enter "`text`" to the alert - When I accept the alert - When I dismiss the alert - Then I should see an alert - Then I should see an alert within `timeout` seconds - Then I should see an alert containing "`text`" - Then I should see an alert containing "`text`" within `timeout` seconds
+
+- Mouse
+
+ - When I mouse over the element with xpath "`xpath`"
+ - When I mouse out of the element with xpath "`xpath`"
+
+- Downloads
+
+ - Then the file "`filename`" with contents "`text`" should have been downloaded within `timeout` seconds
+ - Then the file "`filename`" should have been downloaded within `timeout` seconds
+
+- Persona interaction & variables
+
+ - When I set "`key`" to the text of "`id|name`"
+ - When I set "`key`" to the attribute "`attr`" of the element with xpath "`xpath`"
+ - When I evaluate the script "`script`" and assign the result to "`key`"
+
+## `behaving.mail` Supported matchers/steps
+
+- When I click the link in the email I received at "`address`"
+- When I parse the email I received at "`address`" and set "`expression`"
+- When I clear the email messages
+- Then I should receive an email at "`address`"
+- Then I should receive an email at "`address`" with subject "`subject`"
+- Then I should receive an email at "`address`" containing "`text`"
+- Then I should receive an email at "`address`" with attachment "`filename`"
+- Then I should not have received any emails at "`address`"
+
+## `behaving.sms` Supported matchers/steps
+
+- When I set "`key`" to the body of the sms I received at "`number`"
+- When I parse the sms I received at "`number`" and set "`expression`"
+- Then I should receive an sms at "`number`"
+- Then I should receive an sms at "`number`" containing "`text`"
+
+## `behaving.notifications.gcm` Supported matchers/steps
+
+- When I send a gcm message "{"to":"deviceID", "data": {"message":"Foo Bar", "badge": 6}}"
+- Then I should receive a gcm notification at "deviceID" containing "{'data': {'message': 'Foo Bar'}}"
+- Then I should have received any gcm notifications at "deviceID"
+
+## `behaving.personas` Supported matchers/steps
+
+- Given "`name`" as the persona
+- When I set "`key`" to "`value`"
+- When I set "`key`" to:
+ """ `some longer body of text`
+ `usually multiline`
+ """
+- When I clone persona "`source`" to "`target`"
+- Then "`key`" is set to "`value`"
+
+## Debugging
+
+- When I pause the tests
+
+## Docker integration
+
+A `Dockerfile` as well as a complete setup using `docker-compose` are provided to help you create selenium grid configurations that run your tests. In addition dev container configuration is included if VSCode is your thing.
+
+In addition we provide pre-build images on docker hub for the `linux/amd64` and `linux/arm64` platforms. Use
+
+```bash
+docker pull behaving/behaving:latest
+```
+
+to pull the image.
+
+## Running behaving tests
+
+You can run all behaving tests as follows:
+
+Start docker compose:
+
+```
+docker-compose up
+```
+
+Open a shell in the behaving container:
+
+```
+docker-compose exec behaving bash
+```
+
+Run behaving tests:
+
+```
+behave tests/features
+```
+
+
+%package help
+Summary: Development documents and examples for behaving
+Provides: python3-behaving-doc
+%description help
+# behaving
+
+[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ggozad/behaving/ci.yml)](https://github.com/ggozad/behaving/actions/workflows/ci.yml)
+[![PyPI](https://img.shields.io/pypi/v/behaving)](https://pypi.org/project/behaving/)
+[![Docker Image Version (latest by date)](https://img.shields.io/docker/v/behaving/behaving)](https://hub.docker.com/repository/docker/behaving/behaving)
+
+_behaving_ is a web application testing framework for
+Behavior-Driven-Development, based on
+[behave](http://pypi.python.org/pypi/behave) and
+[splinter](https://github.com/cobrateam/splinter).
+
+_behave_ is written in Python and is similar to
+[Cucumber](http://cucumber.io/).
+_behaving_ adds the step-libraries for multi-user web/email/sms/gcm
+interactions, and provides the Python _behaving_ namespace so that
+independent step-libraries can work together.
+
+Please refer to _behave_'s excellent
+[documentation](http://behave.readthedocs.io/en/latest/) for a guide on
+how to use it, how to write your custom steps and make it possible to
+extend _behaving_.
+
+## Hello world
+
+Starting to use _behaving_ is pretty easy. Inside some python module,
+add your _features_ consisting each of one or more scenarios. These
+features are Gherkin language files with an extension of `.feature`. In
+the same directory you should have a steps module which imports the
+_behaving_ steps as well as your own custom steps (more on that later in
+the setup\_ section) . Here's a basic example:
+
+```gherkin
+Feature: Text presence
+
+ Background:
+ Given a browser
+
+ Scenario: Search for BDD
+ When I visit "http://www.wikipedia.org/"
+ And I fill in "search" with "BDD"
+ And I press "go"
+ Then I should see "Behavior-driven development" within 5 seconds
+```
+
+## Email, SMS & GCM (Google Cloud Messaging)
+
+While the web is the focus of _behaving_, it also includes simple mocks
+for a mail, SMS and a GCM server. These come with a small collection of
+steps allowing you to do things like:
+
+```gherkin
+Feature: Email & SMS
+
+ Scenario: Click link in an email
+ Given a browser
+ When I send an email to "foo@bar.com" with subject "Hello" and body "Try out this website at http://google.com"
+ And I click the link in the email I received at "foo@bar.com"
+ Then the browser's URL should be "http://google.com/"
+
+ Scenario: Receive SMS with body
+ When I send an sms to "+4745690001" with body "Hello world"
+ Then I should receive an sms at "+4745690001" containing "world"
+
+ Scenario: Receive GCM Notification
+ When I send a gcm message "{"to":"deviceID", "data": {"message": "Foo Bar", "badge": 6}}"
+ Then I should receive a gcm notification at "deviceID" containing "{'data': {'message': 'Foo Bar'}}"
+```
+
+Typically, it will be your web application that sends
+email/sms/notifications and testing it comes down to configuring the
+application to send email/sms/notifications to the mock servers.
+
+## Personas & state
+
+A lot of web apps today rely on multi-user interactions. To help you
+with those interactions, _behaving_ uses the notion of _personas_. A
+persona within a test runs in its own instance of a browser and you can
+have more than one persona (and its browser instance) running
+concurrently. You switch among personas by calling
+
+```gherkin
+Given "PersonaName" as the persona
+```
+
+Personas are also typically implemented as simple dictionaries allowing
+them to carry state, save and reuse variables inside a scenario. When a
+persona is first invoked it is created as an empty dictionary. You can
+predefine personas though with set values.
+
+Let's take the familiar LOTR characters as our test users. On setting up
+the test environment (details later in the setup\_ section), we set up
+the characters basic variables we might be needing in the tests as such:
+
+```python
+PERSONAS = {
+ 'Frodo': dict(
+ fullname=u'Frodo Baggins',
+ email=u'frodo@shire.com',
+ password=u'frodopass',
+ mobile='+4745690001',
+ address: {
+ street: "The Shire",
+ zip: "4321"
+ }
+ ),
+ 'Gandalf': dict(
+ fullname=u'Gandalf the Grey',
+ email=u'gandalf@wizardry.com',
+ password=u'gandalfpass',
+ mobile='+4745690004',
+ address: {
+ street: "Rivendell street 1",
+ zip: "1234"
+ }
+ ),
+ ...
+}
+def before_scenario(context, scenario):
+ ...
+ context.personas = PERSONAS
+```
+
+Within a test and given a persona, you can now use `$var_name` to access
+a variable of a persona. You can also set new variables on personas. So
+the following,
+
+```gherkin
+Given "Gandalf" as the persona
+When I fill in "name" with "$fullname"
+And I fill in "street" with "$address.street"
+And I set "title" to the text of "document-title"
+And I fill in "delete" with "$title"
+And I set "address.country" to the text of "country"
+And I set "postaddress" to:
+"""
+$fullname
+$address.street, $address.zip, $address.country
+"""
+```
+
+would fill in the field with id `name` with `Gandalf the Grey`, `street`
+with `Rivendell street 1` set the variable `title` to the text of the
+element with id `document-title` and reuse the variable `title` to fill
+in the field with id `delete`. It would also store the value of the
+field with id "country" in address[`country`]. The `$var_name` pattern
+is also usable in the text received by steps that expect a body of text,
+which means that the `postaddress` persona variable will contain
+Gandalf's complete snail-mail postage address nicely formatted on
+multiple lines.
+
+## Hello Persona example
+
+Let us assume the following (coming from a real example) scenario.
+[Crypho](https://crypho.com), is an online messaging/sharing site that
+provides users with end-to-end encrypted real-time communications.
+_behaving_ was written to help test Crypho.
+
+In Crypho, teams collaborate in _spaces_. To invite somebody in a
+_space_ the invitee has to share a token with an invitor, so both can
+verify each other's identity.
+
+```gherkin
+Feature: Frodo invites Gandalf to The Shire space
+
+ Given state "the-shire"
+
+ Scenario: Frodo invites Gandalf to The Shire
+
+ Given "Gandalf" as the persona
+ When I log in
+```
+
+Before the scenarios start, the custom step `Given state "the-shire"`
+executes. This preloads the db with data, sets up the server etc. Then
+the scenario executes:
+
+First Gandalf logs in. The step `Given "Gandalf" as the persona`, fires
+up a browser that belongs to the persona Gandalf. The following step,
+`When I log in` is a custom step defined as follows:
+
+```python
+@when('I log in')
+def log_in(context):
+
+ assert context.persona
+ context.execute_steps(u"""
+ When I go to Home
+ Then I should see an element with id "email" within 2 seconds
+ When I fill in "email" with "$email"
+ And I press "send-sms"
+ Then I should see "We have sent you an SMS with a security code" within 2 seconds
+ And I should receive an sms at "$mobile"
+ And "token" should be enabled
+ When I parse the sms I received at "$mobile" and set "Your Crypho code is {token}"
+ And I fill in "token" with "$token"
+ And I fill in "password" with "$password"
+ And I press "login"
+ Then I should see "Crypho" within 5 seconds
+ """)
+```
+
+Observe above how the current persona (Gandalf) parses the sms it
+receives and saves it as "token". Later Gandalf reuses it to fill in the
+two-factor authentication field.
+
+Now that Gandalf is logged in, the test proceeds with Frodo. Frodo will
+log in, and invite Gandalf to a private space.
+
+```gherkin
+Given "Frodo" as the persona
+When I log in
+And I click the link with text that contains "My spaces"
+And I click the link with text that contains "The Shire"
+And I press "invite-members"
+ Then I should see "Invite members" within 1 seconds
+When I fill in "invitees" with "gandalf@wizardry.com"
+And I fill in "invitation-message" with "Come and join us!"
+And I press "send-invitations"
+ Then I should see "Your invitations have been sent" within 2 seconds
+```
+
+Once the invitations are sent we switch back to Gandalf's browser, who
+should have received a notification in his browser, as well as an email.
+He then proceeds to send an sms to Frodo with the token who completes
+the invitation.
+
+```gherkin
+Given "Gandalf" as the persona
+Then I should see "Your invitations have been updated" within 2 seconds
+And I should receive an email at "gandalf@wizardry.com" containing "Frodo Baggins has invited you to join a private workspace in Crypho"
+When I click the link with text that contains "Invitations"
+And I click the link with text that contains "Pending invitations"
+ Then I should see "Come and join us!"
+When I set "token" to the text of "invitation-token"
+And I send an sms to "45699900" with body "$token"
+
+Given "Frodo" as the persona
+ Then I should receive an sms at "45699900"
+When I set "FrodoToken" to the body of the sms I received at "45699900"
+And I click the link with text that contains "Invitations"
+And I click the link with text that contains "Enter authorization token"
+And I fill in "auth-token" with "$FrodoToken"
+And I press "Submit"
+ Then I should see "The invitation has been accepted." within 5 seconds
+ And I should see "Gandalf the Grey has joined the space, invited by Frodo Baggins" within 10 seconds
+```
+
+You can see the test in action on video
+[here](http://vimeo.com/63672466/).
+
+## Setting up a test environment
+
+Start by installing _behaving_ by using either `pip` or `easy_install`.
+This will also install dependencies and create the `behave` script with
+which you invoke your tests. If you prefer using buildout, clone the
+package itself from its repository, it contains already a buildout
+configuration.
+
+Typically you will be having a folder containing all your features and
+steps. For example a directory structure like the following:
+
+```
+features/
+features/mytest.feature
+features/myothertest.feature
+features/environment.py
+features/steps/
+features/steps/steps.py
+```
+
+In the steps directory you will need to import the _behaving_ steps you
+need. You can also define your own steps. So `steps.py` might look like:
+
+```python
+from behave import when
+from behaving.web.steps import *
+from behaving.sms.steps import *
+from behaving.mail.steps import *
+from behaving.notifications.gcm.steps import *
+from behaving.personas.steps import *
+
+@when('I go to home')
+def go_to_home(context):
+ context.browser.visit('https://web/')
+```
+
+In `environment.py` you specify settings as well the things that need to
+happen at various stages of testing, i.e. before and after everything, a
+feature run, or a scenario run. For convenience you can import and reuse
+`behaving.environment` which will perform default actions like closing
+all browsers after a scenario, clean the email folder etc.
+
+It is also possible to use `behaving.web.environment`,
+`behaving.mail.environment`, `behaving.sms.environment` and
+`behaving.personas.environment` on their own, if you don't have need for
+SMS for example.
+
+An example of an environment that does simply set some variables and
+then rely on default actions for the various stages, might look like the
+following:
+
+```python
+import os
+from behaving import environment as benv
+
+PERSONAS = {}
+
+def before_all(context):
+ import mypackage
+ context.attachment_dir = os.path.join(os.path.dirname(mypackage.__file__), 'tests/data')
+ context.sms_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/sms/')
+ context.gcm_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/gcm/')
+ context.mail_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/mail/')
+ benv.before_all(context)
+
+
+def after_all(context):
+ benv.after_all(context)
+
+
+def before_feature(context, feature):
+ benv.before_feature(context, feature)
+
+
+def after_feature(context, feature):
+ benv.after_feature(context, feature)
+
+
+def before_scenario(context, scenario):
+ benv.before_scenario(context, scenario)
+ context.personas = PERSONAS
+
+def after_scenario(context, scenario):
+ benv.after_scenario(context, scenario)
+```
+
+The following variables are supported and can be set to override
+defaults:
+
+- `screenshots_dir` (the path where screenshots will be saved. If it
+ is set, any failure in a scenario will result in a screenshot of the
+ browser at the time when the failure happened.)
+- `attachment_dir` (the path where file attachments can be found)
+- `sms_path` (the path to be used by `smsmock` to save sms. Defaults
+ to `current_dir/sms` )
+- `gcm_path` (the path to be used by `gcmmock` to save gcm
+ notifications. Defaults to `current_dir/gcm` )
+- `mail_path` (the path to be used by `mailmock` to save mail.
+ Defaults to `current_dir/mail` )
+- `default_browser`
+- `default_browser_size` (tuple (width, height), applied to each
+ browser as it's created)
+- `max_browser_attempts` (how many times to retry creating the browser
+ if it fails)
+- `remote_webdriver_url` (points to your selenium hub url or remote
+ webdriver. Defaults to `None`)
+- `browser_args` (a dict of additional keyword arguments used when
+ creating a browser)
+- `base_url` (the base url for a browser, allows you to use relative
+ paths)
+- `accept_ssl_certs` (setting to `True` will accept self-signed/invalid
+ certificates. Defaults to `None`)
+
+You can run the tests simply by issuing
+
+```sh
+./bin/behave ./features
+```
+
+For chrome and docker issues, the code below is useful
+
+```python
+from selenium.webdriver.chrome.options import Options
+chrome_options = Options()
+chrome_options.add_argument('--no-sandbox')
+context.browser_args = {
+ 'options': chrome_options
+}
+```
+
+## Mail, GCM and SMS mock servers
+
+When _behaving_ is installed, it creates three scripts to help you test
+mail, gcm and sms, `mailmock`, `gcmmock` and `smsmock` respectively. You
+can directly invoke them before running your tests, they all take a port
+as well as the directory to output data as parameters. For example,
+
+```sh
+./bin/smsmock -p 8081 -o ./var/sms
+./bin/gcmmock -p 8082 -o ./var/notifications/gcm
+./bin/mailmock -p 8083 -o ./var/mail [--no-stdout]
+```
+
+## `behaving.web` Supported matchers/steps
+
+- Browsers
+
+ - Given a browser [opens the default browser, i.e. Firefox]
+ - Given `brand` as the default browser [sets the default browser to be `brand`, this is the browser name when using the remote webdriver or Firefox, Chrome, Safari]
+ - Given the electron app "`app_path`" [for use with electron-based desktop apps]
+ - Given browser "`name`" [opens the browser named `name`]
+ - When I reload
+ - When I go back
+ - When I go forward
+ - When I resize the browser to `width`x`height`
+ - When I resize the viewport to `width`x`height`
+ - When I take a screenshot [will save a screenshot of the browser if `screenshots_dir` is set on the environment. Also, if `screenshots_dir` is set, all failing tests will result in a screenshot.]
+ - When I execute the script "`script`"
+ - When I set the cookie "`key`" to "`value`"
+ - When I delete the cookie "`key`"
+ - When I delete all cookies
+ - When I close the browser "`name`"
+
+- Frames
+
+ - When I switch to frame with css "`css`"
+ - When I switch back to the main page
+
+- Windows
+
+ - When I open a new window named "`name`" at "`url`"
+ - When I name the current window "`name`"
+ - When I switch to the window named "`name`"
+
+- URLs
+
+ - Given the base url "`url`" [sets the base url to `url`, alternatively set `context.base_url` directly in `environment.py`]
+ - When I visit "`url`"
+ - When I go to "`url`"
+ - When I parse the url path and set "`{expression}`"
+ - Then the browser's URL should be "`url`"
+ - Then the browser's URL should contain "`text`"
+ - Then the browser's URL should not contain "`text`"
+
+- Links
+
+ - When I click the link to "`url`"
+ - When I click the link to a url that contains "`url`"
+ - When I click the link with text "`text`"
+ - When I click the link with text that contains "`text`"
+
+- Text, element & class presence
+
+ - When I wait for `timeout` seconds
+ - When I show the element with id "`id`"
+ - When I hide the element with id "`id`"
+
+ - Text
+
+ - Then I should see "`text`"
+ - Then I should not see "`text`"
+ - Then I should see "`text`" within `timeout` seconds
+ - Then I should not see "`text`" within `timeout` seconds
+
+ - ID
+ - Then I should see an element with id "`id`"
+ - Then I should not see an element with id "`id`"
+ - Then I should see an element with id "`id`" within `timeout` seconds
+ - Then I should not see an element with id "`id`" within `timeout` seconds
+
+- CSS
+
+ - Existence
+ - Then I should see an element with the css selector "`selector`"
+ - Then I should not see an element with the css selector "`selector`"
+ - Then I should see an element with the css selector "`selector`" within `timeout` seconds
+ - Then I should not see an element with the css selector "`selector`" within `timeout` seconds
+ - Then I should see `n` elements with the css selector "`css`"
+ - Then I should see at least `n` elements with the css selector "`css`" within `timeout` seconds
+ - Visibility
+ - Then the element with the css selector "`css`" should be visible
+ - Then the element with the css selector "`css`" should be visible within `timeout` seconds
+ - Then the element with the css selector "`css`" should not be visible
+ - Then the element with the css selector "`css`" should be visible within `timeout` seconds
+ - Then {n:d} elements with the css selector "`css`" should be visible
+ - Then {n:d} elements with the css selector "`css`" should be visible within `timeout` seconds
+ - Then at least {n:d} elements with the css selector "`css`" should be visible
+ - Then at least {n:d} elements with the css selector "`css`" should be visible within `timeout` seconds
+ - Existence of a class on an element
+ - Then the element with xpath "`xpath`" should have the class "`cls`"
+ - Then the element with xpath "`xpath`" should not have the class "`cls`"
+ - Then the element with xpath "`xpath`" should have the class "`cls`" within `timeout` seconds
+ - Then the element with xpath "`xpath`" should not have the class "`cls`" within `timeout` seconds
+ - Then "`name`" should have the class "`cls`"
+ - Then "`name`" should not have the class "`cls`"
+ - Then "`name`" should have the class "`cls`" within `timeout` seconds
+ - Then "`name`" should not have the class "`cls`" within `timeout:d` seconds
+ - XPath
+ - Then I should see an element with xpath "`xpath`"
+ - Then I should not see an element with xpath "`xpath`"
+ - Then I should see an element with xpath "`xpath`" within `timeout` seconds
+ - Then I should not see an element with xpath "`xpath`" within `timeout` seconds
+
+- Forms
+
+ - When I fill in "`name|id`" with "`value`"
+ - When I clear field "`name|id`"
+ - When I type "`value`" to "`name|id`" [same as fill, but happens slowly triggering keyboard events]
+ - When I choose "`value`" from "`name`"
+ - When I check "`name|id`"
+ - When I uncheck "`name|id`"
+ - When I toggle "`name|id`"
+ - When I select "`value`" from "`name`""
+ - When I select by text "`text`" from "`name`""
+ - When I press "`name|id|text|innerText`"
+ - When I press the element with xpath "`xpath`"
+ - When I attach the file "`path`" to "`name`"
+ - When I set the innner HTML of the element with id "`id`" to "`contents`" [Sets html on a `contenteditable` element with id `id` to `contents`]
+ - When I set the innner HTML of the element with class "`class`" to "`contents`"
+ - When I set the innner HTML of the element with class "`class`" to "`contents`"
+ - When I send "`KEY`" to "`name`"
+ - When I focus on "`name`"
+ - Then field "`name`" should have the value "`value`"
+ - Then field "`name`" should have the value "`value`" within `timeout` seconds
+ - Then the selection "`name`" should have the options "`valueA, valueB`" selected
+ - Then "`name`" should be enabled
+ - Then "`name`" should be disabled
+ - Then "`name`" should not be enabled
+ - Then "`name`" should be valid
+ - Then "`name`" should be invalid
+ - Then "`name`" should not be valid
+ - Then "`name`" should be required
+ - Then "`name`" should not be required
+
+- HTML tables
+
+ - Then the table with id "`id`" should be
+ | header1 | header2 | ... | header(m) |
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+ ...
+ | celln0 | celln1 | ... | cellnm |
+
+ - Then the table with xpath "`xpath`" should be
+ | header1 | header2 | ... | header(m) |
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+ ...
+ | celln0 | celln1 | ... | cellnm |
+
+ - Then the table with id "`id`" should contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with xpath "`xpath`" should contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with id "`id`" should not contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then the table with xpath "`xpath`" should not contain the rows
+ | cell00 | cell01 | ... | cell0m |
+ | cell10 | cell11 | ... | cell1m |
+
+ - Then row `row_no` in the table with id "`id`" should be
+ | cell00 | cell01 | ... | cell0m |
+
+ - Then row `row_no` in the table with xpath "`xpath`" should be
+ | cell00 | cell01 | ... | cell0m |
+
+ - Then the value of the cell in row `row_no`, column `col_no` in the table with id "`id`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column `col_no` in the table with xpath "`xpath`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column "`col_header`" in the table with id "`id`" should be "`value`"
+
+ - Then the value of the cell in row `row_no`, column "`col_header`" in the table with xpath "`xpath`" should be "`value`"
+
+- Alerts & prompts
+
+ - When I enter "`text`" to the alert - When I accept the alert - When I dismiss the alert - Then I should see an alert - Then I should see an alert within `timeout` seconds - Then I should see an alert containing "`text`" - Then I should see an alert containing "`text`" within `timeout` seconds
+
+- Mouse
+
+ - When I mouse over the element with xpath "`xpath`"
+ - When I mouse out of the element with xpath "`xpath`"
+
+- Downloads
+
+ - Then the file "`filename`" with contents "`text`" should have been downloaded within `timeout` seconds
+ - Then the file "`filename`" should have been downloaded within `timeout` seconds
+
+- Persona interaction & variables
+
+ - When I set "`key`" to the text of "`id|name`"
+ - When I set "`key`" to the attribute "`attr`" of the element with xpath "`xpath`"
+ - When I evaluate the script "`script`" and assign the result to "`key`"
+
+## `behaving.mail` Supported matchers/steps
+
+- When I click the link in the email I received at "`address`"
+- When I parse the email I received at "`address`" and set "`expression`"
+- When I clear the email messages
+- Then I should receive an email at "`address`"
+- Then I should receive an email at "`address`" with subject "`subject`"
+- Then I should receive an email at "`address`" containing "`text`"
+- Then I should receive an email at "`address`" with attachment "`filename`"
+- Then I should not have received any emails at "`address`"
+
+## `behaving.sms` Supported matchers/steps
+
+- When I set "`key`" to the body of the sms I received at "`number`"
+- When I parse the sms I received at "`number`" and set "`expression`"
+- Then I should receive an sms at "`number`"
+- Then I should receive an sms at "`number`" containing "`text`"
+
+## `behaving.notifications.gcm` Supported matchers/steps
+
+- When I send a gcm message "{"to":"deviceID", "data": {"message":"Foo Bar", "badge": 6}}"
+- Then I should receive a gcm notification at "deviceID" containing "{'data': {'message': 'Foo Bar'}}"
+- Then I should have received any gcm notifications at "deviceID"
+
+## `behaving.personas` Supported matchers/steps
+
+- Given "`name`" as the persona
+- When I set "`key`" to "`value`"
+- When I set "`key`" to:
+ """ `some longer body of text`
+ `usually multiline`
+ """
+- When I clone persona "`source`" to "`target`"
+- Then "`key`" is set to "`value`"
+
+## Debugging
+
+- When I pause the tests
+
+## Docker integration
+
+A `Dockerfile` as well as a complete setup using `docker-compose` are provided to help you create selenium grid configurations that run your tests. In addition dev container configuration is included if VSCode is your thing.
+
+In addition we provide pre-build images on docker hub for the `linux/amd64` and `linux/arm64` platforms. Use
+
+```bash
+docker pull behaving/behaving:latest
+```
+
+to pull the image.
+
+## Running behaving tests
+
+You can run all behaving tests as follows:
+
+Start docker compose:
+
+```
+docker-compose up
+```
+
+Open a shell in the behaving container:
+
+```
+docker-compose exec behaving bash
+```
+
+Run behaving tests:
+
+```
+behave tests/features
+```
+
+
+%prep
+%autosetup -n behaving-3.1.5
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-behaving -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Mon May 15 2023 Python_Bot <Python_Bot@openeuler.org> - 3.1.5-1
+- Package Spec generated