Remove code for ui tests
This commit is contained in:
Родитель
27f6f8fe9b
Коммит
0c845636a9
|
@ -177,12 +177,6 @@ setup-ui-tests:
|
|||
# Also update category counts (denormalized field)
|
||||
$(PYTHON_COMMAND) manage.py cron category_totals
|
||||
|
||||
run-auto-approve:
|
||||
$(PYTHON_COMMAND) tests/ui/run_auto_approve.py
|
||||
|
||||
.PHONY: run-ui-tests
|
||||
run-ui-tests:
|
||||
docker-compose -f docker-compose.yml -f tests/ui/docker-compose.selenium.yml exec selenium-firefox tox -e ui-tests
|
||||
|
||||
.PHONY: perf-tests
|
||||
perf-tests: setup-ui-tests
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
version: "2.4"
|
||||
|
||||
# If you're changing the &env mapping
|
||||
# please make sure to update tests/ui/docker-compose.selenium.yml
|
||||
# accordingly.
|
||||
x-env-mapping: &env
|
||||
environment:
|
||||
- CELERY_BROKER_URL=amqp://olympia:olympia@rabbitmq/olympia
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
-r tests.txt
|
||||
|
||||
FoxPuppet==1.0.3 \
|
||||
--hash=sha256:356a53ecea108e11526557e78ae9f7587cbe6113912ef36aa7b633c4a3318a96 \
|
||||
--hash=sha256:d60e2b0d39bb9fdc2097f72e1a3e88d3fbb69a5b09fd78c26ebcd4da4956aa00
|
||||
fxapom==1.10.2 \
|
||||
--hash=sha256:56fdff0a0f0ea58831337e3a859971f98c59fd028ebc14baa8e37ae08a40efa0 \
|
||||
--hash=sha256:5fb902afaaa9d9b82b5d1d54b9e19f1f4c9be128deb3b0e0ac82a9303f76000f
|
||||
PyJWT==1.7.1 \
|
||||
--hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \
|
||||
--hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96
|
||||
PyPOM==2.2.0 \
|
||||
--hash=sha256:4bdd57fceb72d7e6a3645cf6c9322f490d9cfb5d777eac2c851a3b658b813939 \
|
||||
--hash=sha256:6772ec99f0a21a5bdc8c092007a8c813ed18359e67ed70258bbb233df5e28829
|
||||
pytest-base-url==1.4.1 \
|
||||
--hash=sha256:31e42366a5fc22f450b398837dc819bb7569f5e6bd5d74e494b2b9ec239876d1 \
|
||||
--hash=sha256:7425e8163345494ac7f544e99c6f3e5a08f4228bee5e26013b98c462a4d31f6e
|
||||
pytest-html==2.0.1 \
|
||||
--hash=sha256:933da7a5e71e5eace9e475441ed88a684f20f6198aa36516cb947ac05edd9921 \
|
||||
--hash=sha256:bc40553ca2a1835479c2caf7d48604502cd66d0c5db58ddbca53d74946ee71bd
|
||||
pytest-firefox==0.1.1 \
|
||||
--hash=sha256:d91ddd9b7090986d6e45df38830328d7178e95a846eafb1a975a12b7eb5a9fdb \
|
||||
--hash=sha256:7c7acb4dc3d068a6d356797f18731b6ad2f64b5681043c0119f4b821bf20ea07
|
||||
pytest-instafail==0.4.1.post0 \
|
||||
--hash=sha256:9730f6e56c8cfc1a9eec7dd4a1041abf64f08ef81ea1a7d53ea084cadbe234b9 \
|
||||
--hash=sha256:cc5007720424aeabadcf178fc0aecb3b21e09950b9e93638f3eb16c47f5fa917
|
||||
pytest-metadata==1.8.0 \
|
||||
--hash=sha256:c29a1fb470424926c63154c1b632c02585f2ba4282932058a71d35295ff8c96d \
|
||||
--hash=sha256:2071a59285de40d7541fde1eb9f1ddea1c9db165882df82781367471238b66ba
|
||||
pytest-rerunfailures==8.0 \
|
||||
--hash=sha256:17c1236b9f8463f74a5df6c807f301c53c2ea1ab81b7509b7e23fab3b7cbe812 \
|
||||
--hash=sha256:4997cda1b82f74cc0c280f6a5a0470d36deb318e3d8bd6da71efc32a3e0c9ae4
|
||||
pytest-selenium==1.17.0 \
|
||||
--hash=sha256:caf049839d12297e01f0521a968e44ae854f4eca1afd80b28f6a2510df02c883 \
|
||||
--hash=sha256:e8034ebabc3b55fad57bfb97e7b0b2137532dbc65f33706e1ce1ed8e547caa1a
|
||||
pytest-variables==1.9.0 \
|
||||
--hash=sha256:ccf4afcd70de1f5f18b4463758a19f24647a9def1805f675e80db851c9e00ac0 \
|
||||
--hash=sha256:f79851e4c92a94c93d3f1d02377b5ac97cc8800392e87d108d2cbfda774ecc2a
|
||||
selenium==3.141.0 \
|
||||
--hash=sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c \
|
||||
--hash=sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d
|
||||
WebOb==1.8.5 \
|
||||
--hash=sha256:36db8203c67023d68c1b00208a7bf55e3b10de2aa317555740add29c619de12b \
|
||||
--hash=sha256:05aaab7975e0ee8af2026325d656e5ce14a71f1883c52276181821d6d5bf7086
|
||||
zope.event==4.4 \
|
||||
--hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \
|
||||
--hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7
|
||||
zope.interface==4.7.1 \
|
||||
--hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \
|
||||
--hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \
|
||||
--hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \
|
||||
--hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \
|
||||
--hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \
|
||||
--hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \
|
||||
--hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \
|
||||
--hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \
|
||||
--hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \
|
||||
--hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \
|
||||
--hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \
|
||||
--hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \
|
||||
--hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \
|
||||
--hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \
|
||||
--hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \
|
||||
--hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \
|
||||
--hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \
|
||||
--hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \
|
||||
--hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \
|
||||
--hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \
|
||||
--hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \
|
||||
--hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \
|
||||
--hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \
|
||||
--hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \
|
||||
--hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \
|
||||
--hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \
|
||||
--hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \
|
||||
--hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \
|
||||
--hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \
|
||||
--hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \
|
||||
--hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \
|
||||
--hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \
|
||||
--hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \
|
||||
--hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \
|
||||
--hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \
|
||||
--hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \
|
||||
--hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \
|
||||
--hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \
|
||||
--hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25
|
||||
pytest-fxa==1.4.0 \
|
||||
--hash=sha256:778dfdb019f1e0af8744704fe5f7ac5c08fd5d45ff054023b0a18d5f99d737f1 \
|
||||
--hash=sha256:b75967e74e9b2f3ffa5558421fdf61c7fff5948fc9d7e357e7147c682988ecc1
|
||||
zope.component==4.6 \
|
||||
--hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6
|
||||
zope.deferredimport==4.3.1 \
|
||||
--hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \
|
||||
--hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a
|
||||
zope.deprecation==4.4.0 \
|
||||
--hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 \
|
||||
--hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df
|
||||
zope.hookable==4.2.0 \
|
||||
--hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \
|
||||
--hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \
|
||||
--hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \
|
||||
--hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \
|
||||
--hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \
|
||||
--hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \
|
||||
--hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \
|
||||
--hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \
|
||||
--hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \
|
||||
--hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2
|
||||
zope.proxy==4.3.2 \
|
||||
--hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \
|
||||
--hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \
|
||||
--hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \
|
||||
--hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \
|
||||
--hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 \
|
||||
--hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \
|
||||
--hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \
|
||||
--hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \
|
||||
--hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \
|
||||
--hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \
|
||||
--hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17
|
|
@ -1,200 +0,0 @@
|
|||
# Integration Tests for the [Mozilla Addons Website][amo].
|
||||
## How to run the tests locally
|
||||
### Clone the repository
|
||||
|
||||
If you have cloned this project already then you can skip this, otherwise you'll
|
||||
need to clone this repo using Git. If you do not know how to clone a GitHub
|
||||
repository, check out this [help page][git-clone] from GitHub.
|
||||
|
||||
If you think you would like to contribute to the tests by writing or maintaining
|
||||
them in the future, it would be a good idea to create a fork of this repository
|
||||
first, and then clone that. GitHub also has great instructions for
|
||||
[forking a repository][git-fork].
|
||||
|
||||
### Server Install
|
||||
|
||||
Follow the instructions found [here][addons-server-docs].
|
||||
|
||||
### Server setup for tests
|
||||
|
||||
The instructions to setup addons-server for selenium integration tests are found [here][addons-server-selenium-testing].
|
||||
|
||||
### Run the tests
|
||||
|
||||
The selenium tests can be run in a separate docker-compose file, specifically ``tests/ui/docker-compose.selenium.yml``. This image contains Firefox Nightly. [tox][Tox]
|
||||
is our test environment manager and [pytest][pytest] is the test runner.
|
||||
|
||||
To run the tests, execute the commands below:
|
||||
```sh
|
||||
# Make sure all required containers are running.
|
||||
docker-compose -f docker-compose.yml -f tests/ui/docker-compose.selenium.yml up -d
|
||||
docker-compose -f docker-compose.yml -f tests/ui/docker-compose.selenium.yml exec selenium-firefox tox -e ui-tests
|
||||
```
|
||||
|
||||
### Adding a test
|
||||
|
||||
The tests are written in Python using a POM, or Page Object Model. The plugin we use for this is called [pypom][pypom]. Please read the documentation there for good examples
|
||||
on how to use the Page Object Model when writing tests.
|
||||
|
||||
The pytest plugin that we use for running tests has a number of advanced command
|
||||
line options available too. The full documentation for the plugin can be found [here][pytest-selenium].
|
||||
|
||||
## Additional Information
|
||||
|
||||
The tests run against the newest version of the [AMO][amo] frontend using a docker image provided by [addons-frontend][addons-frontend]. You can view the frontend after the build has been completed at ```olympia.test``` which is the default these days.
|
||||
|
||||
### Watching a test run
|
||||
|
||||
The tests are run on a live version of Firefox, but they are run headless. To access the container where the tests are run to view them follow these steps. NOTE: You must have the appropriate containers set up. Please follow the instructions in the [docs][ReadTheDocs] first:
|
||||
|
||||
1. Make sure all of the containers are running:
|
||||
```sh
|
||||
docker-compose ps
|
||||
```
|
||||
If not, start them detached:
|
||||
```sh
|
||||
docker-compose -f docker-compose.yml -f tests/ui/docker-compose.selenium.yml up -d
|
||||
```
|
||||
|
||||
2. Copy the port that is forwarded for the ```selenium-firefox``` image:
|
||||
```sh
|
||||
0.0.0.0:32771->5900/tcp
|
||||
```
|
||||
Note: Your port may not match what is seen here.
|
||||
|
||||
You will want to copy what ever IP address and port is before the ```->5900/tcp```.
|
||||
|
||||
3. Open your favorite VNC viewer and type in, or paste that address.
|
||||
4. The password is ```secret```.
|
||||
5. The viewer should open a window with a Ubuntu logo. If that happens you are connected to the ```selenium-firefox``` image and if you start the test, you should see a Firefox window open and the tests running.
|
||||
|
||||
If you have [geckodriver][geckodriver] installed, and Firefox can be launched from your terminal, you can run this command:
|
||||
|
||||
```sh
|
||||
tox -r -e ui-tests
|
||||
```
|
||||
This will open a Firefox window with the tests running.
|
||||
|
||||
### Firefox setup
|
||||
|
||||
The preferences used to setup Firefox are here:
|
||||
```sh
|
||||
firefox_options.set_preference(
|
||||
'extensions.install.requireBuiltInCerts', False)
|
||||
firefox_options.set_preference('xpinstall.signatures.required', False)
|
||||
firefox_options.set_preference('extensions.webapi.testing', True)
|
||||
firefox_options.set_preference('ui.popup.disable_autohide', True)
|
||||
firefox_options.add_argument('-foreground')
|
||||
firefox_options.log.level = 'trace'
|
||||
```
|
||||
These shouldn't need to be touched as they allow for unsigned addon installation as well as
|
||||
disabling the autohide function and setting the Firefox browser to run headless.
|
||||
|
||||
If you do need to edit these settings, as mentioned above please visit the file ```conftest.py``` within this directory.
|
||||
|
||||
### Mobile and Desktop testing
|
||||
|
||||
If you would like to add or edit tests please consider that these are run on both a mobile resolution and a desktop resolution. The mobile resolution is ```738x414 (iPhone 7+)```, the desktop resolution is: ```1920x1080```. Your tests should be able to work on both.
|
||||
|
||||
|
||||
### Debugging a failure
|
||||
|
||||
Whether a test passes or fails will result in a HTML report being created. This report will have detailed information of the test run and if a test does fail, it will provide geckodriver logs, terminal logs, as well as a screenshot of the browser when the test failed. We use a pytest plugin called [pytest-html][pytest-html] to create this report. The report can be found within the root directory of the project and is named ```ui-test.html```. It should be viewed within a browser.
|
||||
|
||||
[amo]: https://addons.mozilla.org
|
||||
[addons-frontend]: https://github.com/mozilla/addons-frontend/
|
||||
[addons-server-docs]: https://addons-server.readthedocs.io/en/latest/topics/install/docker.html
|
||||
[addons-server-selenium-testing]: https://addons-server.readthedocs.io/en/latest/topics/development/testing.html#selenium-integration-tests
|
||||
[flake8]: http://flake8.pycqa.org/en/latest/
|
||||
[git-clone]: https://help.github.com/articles/cloning-a-repository/
|
||||
[git-fork]: https://help.github.com/articles/fork-a-repo/
|
||||
[geckodriver]: https://github.com/mozilla/geckodriver/releases/tag/v0.19.1
|
||||
[pypom]: http://pypom.readthedocs.io/en/latest/
|
||||
[pytest]: https://docs.pytest.org/en/latest/
|
||||
[pytest-html]: https://github.com/pytest-dev/pytest-html
|
||||
[pytest-selenium]: http://pytest-selenium.readthedocs.org/
|
||||
[ReadTheDocs]: https://addons-server.readthedocs.io/en/latest/topics/development/testing.html#selenium-integration-tests
|
||||
[Selenium]: http://selenium-python.readthedocs.io/index.html
|
||||
[selenium-api]: http://selenium-python.readthedocs.io/locating-elements.html
|
||||
[Tox]: http://tox.readthedocs.io/
|
||||
plugin we use for this is called [pypom][pypom]. Please read the documentation there for good examples
|
||||
on how to use the Page Object Model when writing tests.
|
||||
|
||||
The pytest plugin that we use for running tests has a number of advanced command
|
||||
line options available too. The full documentation for the plugin can be found [here][pytest-selenium].
|
||||
|
||||
## Additional Information
|
||||
|
||||
The tests run against the newest version of the [AMO][amo] frontend using a docker image provided by [addons-frontend][addons-frontend]. You can view the frontend after the build has been completed at ```olympia.test``` which is the default these days.
|
||||
|
||||
### Watching a test run
|
||||
|
||||
The tests are run on a live version of Firefox, but they are run headless. To access the container where the tests are run to view them follow these steps:
|
||||
|
||||
IMPORTANT: Please comment out this line within the ```tests/ui/conftest.py``` file if you would like to view or debug the tests.
|
||||
```sh
|
||||
firefox_options.add_argument('-headless')
|
||||
```
|
||||
|
||||
1. Make sure all of the containers are running:
|
||||
```sh
|
||||
docker-compose ps
|
||||
```
|
||||
If not start them detached:
|
||||
```sh
|
||||
docker-compose -f docker-compose.yml -f tests/ui/docker-compose.selenium.yml up -d
|
||||
```
|
||||
|
||||
2. Copy the port that is forwarded for the ```selenium-firefox``` image:
|
||||
```sh
|
||||
0.0.0.0:32771->5900/tcp
|
||||
```
|
||||
Note: Your port may not match what is seen here.
|
||||
|
||||
You will want to copy what ever IP address and port is before the ```->5900/tcp```.
|
||||
|
||||
3. Open your favorite VNC viewer and type in, or paste that address.
|
||||
4. The password is ```secret```.
|
||||
5. The viewer should open a window with a Ubuntu logo. If that happens you are connected to the ```selenium-firefox``` image and if you start the test, you should see a Firefox window open and the tests running.
|
||||
|
||||
### Firefox setup
|
||||
|
||||
The preferences used to setup Firefox are here:
|
||||
```sh
|
||||
firefox_options.set_preference(
|
||||
'extensions.install.requireBuiltInCerts', False)
|
||||
firefox_options.set_preference('xpinstall.signatures.required', False)
|
||||
firefox_options.set_preference('extensions.webapi.testing', True)
|
||||
firefox_options.set_preference('ui.popup.disable_autohide', True)
|
||||
firefox_options.add_argument('-foreground')
|
||||
firefox_options.add_argument('-headless')
|
||||
firefox_options.log.level = 'trace'
|
||||
```
|
||||
These shouldn't need to be touched as they allow for unsigned addon installation as well as
|
||||
disabling the autohide function and setting the Firefox browser to run headless.
|
||||
|
||||
If you do need to edit these settings, as mentioned above please visit the file ```conftest.py``` within this directory.
|
||||
|
||||
### Mobile and Desktop testing
|
||||
|
||||
If you would like to add or edit tests please consider that these are run on both a mobile resolution and a desktop resolution. The mobile resolution is ```738x414 (iPhone 7+)```, the desktop resolution is: ```1920x1080```. Your tests should be able to work on both.
|
||||
|
||||
|
||||
### Debugging a failure
|
||||
|
||||
Whether a test passes or fails will result in a HTML report being created. This report will have detailed information of the test run and if a test does fail, it will provide geckodriver logs, terminal logs, as well as a screenshot of the browser when the test failed. We use a pytest plugin called [pytest-html][pytest-html] to create this report. The report can be found within the root directory of the project and is named ```ui-test.html```. It should be viewed within a browser.
|
||||
|
||||
[amo]: https://addons.mozilla.org
|
||||
[addons-frontend]: https://github.com/mozilla/addons-frontend/
|
||||
[addons-server-docs]: https://addons-server.readthedocs.io/en/latest/topics/install/docker.html
|
||||
[flake8]: http://flake8.pycqa.org/en/latest/
|
||||
[git-clone]: https://help.github.com/articles/cloning-a-repository/
|
||||
[git-fork]: https://help.github.com/articles/fork-a-repo/
|
||||
[geckodriver]: https://github.com/mozilla/geckodriver/releases/tag/v0.19.1
|
||||
[pypom]: http://pypom.readthedocs.io/en/latest/
|
||||
[pytest]: https://docs.pytest.org/en/latest/
|
||||
[pytest-html]: https://github.com/pytest-dev/pytest-html
|
||||
[pytest-selenium]: http://pytest-selenium.readthedocs.org/
|
||||
[Selenium]: http://selenium-python.readthedocs.io/index.html
|
||||
[selenium-api]: http://selenium-python.readthedocs.io/locating-elements.html
|
||||
[Tox]: http://tox.readthedocs.io/
|
|
@ -1,116 +0,0 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from pages.desktop.devhub import DevHub
|
||||
|
||||
# Window resolutions
|
||||
DESKTOP = (1080, 1920)
|
||||
MOBILE = (414, 738)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def base_url(base_url):
|
||||
return "http://olympia.test"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def sensitive_url(request, base_url):
|
||||
# Override sensitive url check
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def firefox_options(firefox_options):
|
||||
"""Firefox options.
|
||||
|
||||
These options configure firefox to allow for addon installation,
|
||||
as well as allowing it to run headless.
|
||||
|
||||
'extensions.install.requireBuiltInCerts', False: This allows extensions to
|
||||
be installed with a self-signed certificate.
|
||||
'xpinstall.signatures.required', False: This allows an extension to be
|
||||
installed without a certificate.
|
||||
'extensions.webapi.testing', True: This is needed for whitelisting
|
||||
mozAddonManager
|
||||
'-foreground': Firefox will run in the foreground with priority
|
||||
'-headless': Firefox will run headless
|
||||
|
||||
"""
|
||||
firefox_options.set_preference(
|
||||
'extensions.install.requireBuiltInCerts', False
|
||||
)
|
||||
firefox_options.set_preference('xpinstall.signatures.required', False)
|
||||
firefox_options.set_preference('extensions.webapi.testing', True)
|
||||
firefox_options.set_preference('ui.popup.disable_autohide', True)
|
||||
firefox_options.set_preference('devtools.console.stdout.content', True)
|
||||
firefox_options.add_argument('-foreground')
|
||||
firefox_options.log.level = 'trace'
|
||||
return firefox_options
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def firefox_notifications(notifications):
|
||||
return notifications
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
scope='function',
|
||||
params=[DESKTOP, MOBILE],
|
||||
ids=['Resolution: 1080x1920', 'Resolution: 414x738'],
|
||||
)
|
||||
def selenium(selenium, request):
|
||||
"""Fixture to set custom selenium parameters.
|
||||
|
||||
This fixture will also parametrize all of the tests to run them on both a
|
||||
Desktop resolution and a mobile resolution.
|
||||
|
||||
Desktop size: 1920x1080
|
||||
Mobile size: 738x414 (iPhone 7+)
|
||||
|
||||
"""
|
||||
# Skip mobile test with marker 'desktop_only'
|
||||
marker = request.node.get_closest_marker('desktop_only')
|
||||
if marker and request.param == MOBILE:
|
||||
pytest.skip('Skipping mobile test')
|
||||
selenium.set_window_size(*request.param)
|
||||
return selenium
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fxa_account(request):
|
||||
"""Fxa account to use during tests that need to login.
|
||||
|
||||
Returns the email and password of the fxa account set in Makefile-docker.
|
||||
|
||||
"""
|
||||
try:
|
||||
fxa_account.email = os.environ['UITEST_FXA_EMAIL']
|
||||
fxa_account.password = os.environ['UITEST_FXA_PASSWORD']
|
||||
except KeyError:
|
||||
if request.node.get_closest_marker('fxa_login'):
|
||||
pytest.skip(
|
||||
'Skipping test because no fxa account was found.'
|
||||
' Are FXA_EMAIL and FXA_PASSWORD environment variables set?')
|
||||
return fxa_account
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def devhub_login(selenium, base_url, fxa_account):
|
||||
"""Log into the devhub."""
|
||||
selenium.get('{}/developers'.format(base_url))
|
||||
devhub = DevHub(selenium, base_url)
|
||||
return devhub.login(fxa_account.email, fxa_account.password)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def devhub_upload(devhub_login):
|
||||
"""Upload addon to devhub.
|
||||
|
||||
This uses a webextension fixture within addons-server to
|
||||
upload as a new addon.
|
||||
|
||||
"""
|
||||
devhub = devhub_login
|
||||
addon = devhub.upload_addon('ui-test_devhub_ext-1.0.zip')
|
||||
return addon.fill_addon_submission_form()
|
|
@ -1,32 +0,0 @@
|
|||
version: "2.4"
|
||||
|
||||
x-env-mapping: &env
|
||||
environment:
|
||||
- CELERY_BROKER_URL=amqp://olympia:olympia@rabbitmq/olympia
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
- DATABASES_DEFAULT_URL=mysql://root:@mysqld/olympia
|
||||
- ELASTICSEARCH_LOCATION=elasticsearch:9200
|
||||
- MEMCACHE_LOCATION=memcached:11211
|
||||
- MYSQL_DATABASE=olympia
|
||||
- MYSQL_ROOT_PASSWORD=docker
|
||||
- OLYMPIA_SITE_URL=http://olympia.test
|
||||
- PYTHONDONTWRITEBYTECODE=1
|
||||
- PYTHONUNBUFFERED=1
|
||||
- TERM=xterm-256color
|
||||
- UITEST_FXA_EMAIL
|
||||
- UITEST_FXA_PASSWORD=uitester
|
||||
- MOZ_HEADLESS
|
||||
- PYTEST_ADDOPTS=${PYTEST_ADDOPTS}
|
||||
|
||||
services:
|
||||
selenium-firefox:
|
||||
<<: *env
|
||||
image: b4handjr/selenium-firefox:python3-latest
|
||||
volumes:
|
||||
- .:/code
|
||||
expose:
|
||||
- "4444"
|
||||
ports:
|
||||
- "5900"
|
||||
shm_size: 2g
|
||||
network_mode: host
|
|
@ -1,185 +0,0 @@
|
|||
from pypom import Page, Region
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
|
||||
class Base(Page):
|
||||
|
||||
_url = '{base_url}/{locale}'
|
||||
_amo_header = (By.CLASS_NAME, 'Header')
|
||||
|
||||
def __init__(self, selenium, base_url, locale='en-US', **kwargs):
|
||||
super(Base, self).__init__(
|
||||
selenium, base_url, locale=locale, timeout=30, **kwargs)
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.find_element(*self._amo_header).is_displayed())
|
||||
return self
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return Header(self)
|
||||
|
||||
@property
|
||||
def footer(self):
|
||||
return Footer(self)
|
||||
|
||||
@property
|
||||
def logged_in(self):
|
||||
"""Returns True if a user is logged in"""
|
||||
return self.is_element_displayed(*self.header._user_locator)
|
||||
|
||||
@property
|
||||
def search(self):
|
||||
return self.header.SearchBox(self)
|
||||
|
||||
def login(self, email, password):
|
||||
login_page = self.header.click_login()
|
||||
login_page.login(email, password)
|
||||
self.selenium.get(self.base_url)
|
||||
self.wait.until(lambda _: self.logged_in)
|
||||
|
||||
def logout(self):
|
||||
self.header.click_logout()
|
||||
|
||||
|
||||
class Header(Region):
|
||||
|
||||
_root_locator = (By.CLASS_NAME, 'Header')
|
||||
_header_title_locator = (By.CLASS_NAME, 'Header-title')
|
||||
_explore_locator = (By.CSS_SELECTOR, '.SectionLinks > li:nth-child(1) \
|
||||
> a:nth-child(1)')
|
||||
_firefox_logo_locator = (By.CLASS_NAME, 'Header-title')
|
||||
_extensions_locator = (By.CSS_SELECTOR, '.SectionLinks \
|
||||
> li:nth-child(2) > a:nth-child(1)')
|
||||
_login_locator = (By.CLASS_NAME, 'Header-authenticate-button')
|
||||
_logout_locator = (
|
||||
By.CSS_SELECTOR, '.DropdownMenu-items .Header-logout-button')
|
||||
_more_dropdown_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'.Header-SectionLinks .SectionLinks-dropdown')
|
||||
_more_dropdown_link_locator = (By.CSS_SELECTOR, '.DropdownMenuItem a')
|
||||
_themes_locator = (By.CSS_SELECTOR, '.SectionLinks > li:nth-child(3) > \
|
||||
a:nth-child(1)')
|
||||
_user_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'.Header-user-and-external-links .DropdownMenu-button-text')
|
||||
|
||||
def click_explore(self):
|
||||
self.find_element(*self._firefox_logo_locator).click()
|
||||
|
||||
def click_extensions(self):
|
||||
self.find_element(*self._extensions_locator).click()
|
||||
from pages.desktop.extensions import Extensions
|
||||
return Extensions(
|
||||
self.selenium, self.page.base_url).wait_for_page_to_load()
|
||||
|
||||
def click_themes(self):
|
||||
self.find_element(*self._themes_locator).click()
|
||||
from pages.desktop.themes import Themes
|
||||
return Themes(
|
||||
self.selenium, self.page.base_url).wait_for_page_to_load()
|
||||
|
||||
def click_title(self):
|
||||
self.find_element(*self._header_title_locator).click()
|
||||
|
||||
from pages.desktop.home import Home
|
||||
return Home(self.selenium, self.page.base_url).wait_for_page_to_load()
|
||||
|
||||
def click_login(self):
|
||||
self.find_element(*self._login_locator).click()
|
||||
from pages.desktop.login import Login
|
||||
return Login(self.selenium, self.page.base_url)
|
||||
|
||||
def click_logout(self):
|
||||
user = self.find_element(*self._user_locator)
|
||||
logout = self.find_element(*self._logout_locator)
|
||||
action = ActionChains(self.selenium)
|
||||
action.move_to_element(user)
|
||||
action.click()
|
||||
action.pause(2)
|
||||
action.move_to_element(logout)
|
||||
action.pause(2)
|
||||
action.click(logout)
|
||||
action.perform()
|
||||
self.wait.until(lambda s: self.is_element_displayed(
|
||||
*self._login_locator))
|
||||
|
||||
def more_menu(self, item=None):
|
||||
menu = self.find_element(*self._more_dropdown_locator)
|
||||
links = menu.find_elements(*self._more_dropdown_link_locator)
|
||||
# Create an action chain clicking on the elements of the dropdown more
|
||||
# menu. It pauses between each action to account for lag.
|
||||
action = ActionChains(self.selenium)
|
||||
action.move_to_element(menu)
|
||||
action.click()
|
||||
action.pause(2)
|
||||
action.move_to_element(links[item])
|
||||
action.click()
|
||||
action.pause(2)
|
||||
action.perform()
|
||||
|
||||
class SearchBox(Region):
|
||||
|
||||
_root_locator = (By.CLASS_NAME, 'AutoSearchInput')
|
||||
_search_suggestions_list_locator = (
|
||||
By.CLASS_NAME, 'AutoSearchInput-suggestions-list')
|
||||
_search_suggestions_item_locator = (
|
||||
By.CLASS_NAME, 'AutoSearchInput-suggestions-item')
|
||||
_search_textbox_locator = (By.CLASS_NAME, 'AutoSearchInput-query')
|
||||
|
||||
def search_for(self, term, execute=True):
|
||||
textbox = self.find_element(*self._search_textbox_locator)
|
||||
textbox.click()
|
||||
textbox.send_keys(term)
|
||||
# Send 'enter' since the mobile page does not have a submit button
|
||||
if execute:
|
||||
textbox.send_keys(Keys.ENTER)
|
||||
from pages.desktop.search import Search
|
||||
return Search(self.selenium, self.page).wait_for_page_to_load()
|
||||
return self.search_suggestions
|
||||
|
||||
@property
|
||||
def search_suggestions(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(
|
||||
*self._search_suggestions_list_locator)
|
||||
)
|
||||
el_list = self.find_element(*self._search_suggestions_list_locator)
|
||||
items = el_list.find_elements(
|
||||
*self._search_suggestions_item_locator)
|
||||
return [self.SearchSuggestionItem(self.page, el) for el in items]
|
||||
|
||||
class SearchSuggestionItem(Region):
|
||||
|
||||
_item_name = (By.CLASS_NAME, 'SearchSuggestion-name')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._item_name).text
|
||||
|
||||
@property
|
||||
def select(self):
|
||||
self.root.click()
|
||||
from pages.desktop.details import Detail
|
||||
return Detail(self.selenium, self.page).wait_for_page_to_load()
|
||||
|
||||
|
||||
class Footer(Region):
|
||||
|
||||
_root_locator = (By.CSS_SELECTOR, '.Footer-wrapper')
|
||||
_footer_amo_links = (By.CSS_SELECTOR, '.Footer-amo-links')
|
||||
_footer_firefox_links = (By.CSS_SELECTOR, '.Footer-firefox-links')
|
||||
_footer_links = (By.CSS_SELECTOR, '.Footer-links li a')
|
||||
|
||||
@property
|
||||
def addon_links(self):
|
||||
header = self.find_element(*self._footer_amo_links)
|
||||
return header.find_elements(*self._footer_links)
|
||||
|
||||
@property
|
||||
def firefox_links(self):
|
||||
header = self.find_element(*self._footer_firefox_links)
|
||||
return header.find_elements(*self._footer_links)
|
|
@ -1,39 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as expected
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class Categories(Base):
|
||||
|
||||
URL_TEMPLATE = 'extensions/categories/'
|
||||
|
||||
_categories_locator = (By.CLASS_NAME, 'Categories-item')
|
||||
_mobile_categories_locator = (By.CLASS_NAME, 'LandingPage-button')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
expected.invisibility_of_element_located(
|
||||
(By.CLASS_NAME, 'LoadingText')))
|
||||
|
||||
@property
|
||||
def category_list(self):
|
||||
categories = self.find_elements(*self._categories_locator)
|
||||
return [self.CategoryItem(self, el) for el in categories]
|
||||
|
||||
class CategoryItem(Region):
|
||||
|
||||
_link_locator = (By.CLASS_NAME, 'Categories-link')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._link_locator).text
|
||||
|
||||
def click_it(self):
|
||||
if self.find_element(*self._mobile_categories_locator):
|
||||
self.find_element(*self._mobile_categories_locator).click()
|
||||
else:
|
||||
self.find_element(*self._link_locator).click()
|
||||
from pages.desktop.category import Category
|
||||
return Category(self.selenium, self.page)
|
|
@ -1,29 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as expected
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class Category(Base):
|
||||
|
||||
_root_locator = (By.CLASS_NAME, 'Category')
|
||||
_category_header_locator = (By.CLASS_NAME, 'CategoryHeader')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
expected.invisibility_of_element_located(
|
||||
(By.CLASS_NAME, 'LoadingText')))
|
||||
return self
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self.Header(self)
|
||||
|
||||
class Header(Region):
|
||||
|
||||
_category_name_locator = (By.CLASS_NAME, 'SearchContextCard-header')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._category_name_locator).text
|
|
@ -1,27 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Collections(Base):
|
||||
"""Collections page."""
|
||||
_item_locator = (By.CSS_SELECTOR, '.items > div')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(lambda _: len(self.collections) > 0 and
|
||||
self.collections[0].name)
|
||||
return self
|
||||
|
||||
@property
|
||||
def collections(self):
|
||||
collections = self.find_elements(*self._item_locator)
|
||||
return [self.Collection(self, el) for el in collections]
|
||||
|
||||
class Collection(Region):
|
||||
"""Represents an individual collection."""
|
||||
_name_locator = (By.CSS_SELECTOR, '.info > h3')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._name_locator).text
|
|
@ -1,29 +0,0 @@
|
|||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as expected
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class Detail(Base):
|
||||
|
||||
_root_locator = (By.CLASS_NAME, 'Addon-extension')
|
||||
_addon_name_locator = (By.CLASS_NAME, 'AddonTitle')
|
||||
_compatible_locator = (By.CSS_SELECTOR, '.AddonCompatibilityError')
|
||||
_install_button_locator = (By.CLASS_NAME, 'AMInstallButton-button')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
expected.invisibility_of_element_located(
|
||||
(By.CLASS_NAME, 'LoadingText')))
|
||||
return self
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._addon_name_locator).text
|
||||
|
||||
@property
|
||||
def is_compatible(self):
|
||||
return not self.is_element_displayed(*self._compatible_locator)
|
||||
|
||||
def install(self):
|
||||
self.find_element(*self._install_button_locator).click()
|
|
@ -1,180 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.common.exceptions import TimeoutException, NoSuchElementException
|
||||
from selenium.webdriver.support import expected_conditions as expected
|
||||
from olympia.files.tests.test_file_viewer import get_file
|
||||
|
||||
from pages.desktop.base import Base
|
||||
from pages.desktop.devhub_agreement import DevHubAgreement
|
||||
from pages.desktop.devhub_source_code import DevHubSource
|
||||
|
||||
|
||||
class DevHub(Base):
|
||||
"""Developer Hub for uploading addons.
|
||||
|
||||
This page represents the logacy view of the developer hub. It can be used
|
||||
to upload an addon.
|
||||
|
||||
"""
|
||||
URL_TEMPLATE = 'developers/'
|
||||
|
||||
_root_locator = (By.CLASS_NAME, 'DevHub-Navigation')
|
||||
_avatar_locator = (By.CLASS_NAME, 'avatar')
|
||||
_addons_list_locator = (By.CLASS_NAME, 'DevHub-MyAddons-list')
|
||||
_addons_item_locator = (By.CLASS_NAME, 'DevHub-MyAddons-item')
|
||||
_continue_sub_btn_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'.addon-submission-field > button:nth-child(1)',
|
||||
)
|
||||
_override_validation_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'input#id_admin_override_validation',
|
||||
)
|
||||
_overview_locator = (By.CSS_SELECTOR, '.DevHub-Overview h1')
|
||||
_submit_addon_btn_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'.DevHub-MyAddons-item-buttons-submit > .Button:nth-child(1)',
|
||||
)
|
||||
_whats_new_locator = (By.CLASS_NAME, 'DevHub-MyAddons-whatsnew-container')
|
||||
_upload_addon_locator = (By.ID, 'upload-addon')
|
||||
_submit_upload_btn_locator = (By.ID, 'submit-upload-file-finish')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(*self._overview_locator)
|
||||
)
|
||||
return self
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self.Header(self)
|
||||
|
||||
def login(self, email, password):
|
||||
login_page = self.header.click_login()
|
||||
login_page.login(email, password)
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(*self._avatar_locator)
|
||||
)
|
||||
return self
|
||||
|
||||
@property
|
||||
def logged_in(self):
|
||||
return not self.is_element_displayed(*self.header._sign_in_locator)
|
||||
|
||||
@property
|
||||
def addons_list(self):
|
||||
els = self.find_elements(*self._addons_item_locator)
|
||||
return [self.AddonsListItem(self, el) for el in els]
|
||||
|
||||
def upload_addon(self, xpi=None):
|
||||
"""Upload an addon via devhub.
|
||||
|
||||
This will use the override validation option.
|
||||
"""
|
||||
file_path = get_file(xpi)
|
||||
self.selenium.find_element(*self._submit_addon_btn_locator).click()
|
||||
# Accept agreement
|
||||
agreement = False
|
||||
count = 0
|
||||
while agreement is not True and count < 5:
|
||||
try:
|
||||
devhub_agreement = DevHubAgreement(
|
||||
self.selenium, self.base_url
|
||||
).wait_for_page_to_load()
|
||||
devhub_agreement.accept_agreement()
|
||||
except TimeoutException:
|
||||
# Do nothing, the agreement has already been excepted
|
||||
pass
|
||||
except NoSuchElementException:
|
||||
pass
|
||||
try:
|
||||
self.selenium.find_element(
|
||||
*self._continue_sub_btn_locator).click()
|
||||
except NoSuchElementException as e:
|
||||
print(e)
|
||||
self.selenium.refresh
|
||||
count + 1
|
||||
else:
|
||||
agreement = True
|
||||
# Upload
|
||||
upload_finished = False
|
||||
count = 0
|
||||
while upload_finished is not True and count <= 5:
|
||||
try:
|
||||
upload = self.selenium.find_element(
|
||||
*self._upload_addon_locator)
|
||||
upload.send_keys(file_path)
|
||||
self.wait.until(
|
||||
expected.element_to_be_clickable(
|
||||
self._submit_upload_btn_locator)
|
||||
)
|
||||
self.selenium.find_element(
|
||||
*self._submit_upload_btn_locator).click()
|
||||
except TimeoutException:
|
||||
upload_finished = False
|
||||
except Exception as e:
|
||||
self.selenium.refresh
|
||||
if count == 5:
|
||||
raise e
|
||||
count + 1
|
||||
else:
|
||||
upload_finished = True
|
||||
# Submit no source code
|
||||
devhub_source = DevHubSource(
|
||||
self.selenium, self.base_url
|
||||
).wait_for_page_to_load()
|
||||
devhub_source.dont_submit_source_code()
|
||||
from pages.desktop.devhub_submission import DevhubSubmission
|
||||
|
||||
devhub = DevhubSubmission(self.selenium, self.base_url)
|
||||
return devhub.wait_for_page_to_load()
|
||||
|
||||
class AddonsListItem(Region):
|
||||
|
||||
_addon_item_name_locator = (By.CLASS_NAME, 'DevHub-MyAddons-item-name')
|
||||
_addon_edit_link_locator = (By.CLASS_NAME, 'DevHub-MyAddons-item-edit')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._addon_item_name_locator).text
|
||||
|
||||
def edit(self):
|
||||
self.find_element(*self._addon_edit_link_locator).click()
|
||||
from pages.desktop.edit_addon import EditAddon
|
||||
|
||||
edit = EditAddon(self.selenium, self.page.base_url)
|
||||
return edit.wait_for_page_to_load()
|
||||
|
||||
class Header(Region):
|
||||
|
||||
_register_link_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'.DevHub-Navigation-Register > a:nth-child(1)'
|
||||
)
|
||||
_sign_in_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'.DevHub-Navigation-Register > a:nth-child(2)',
|
||||
)
|
||||
_sign_out_locator = (By.CSS_SELECTOR, '.DevHub-Navigation-SignOut > a')
|
||||
|
||||
def click_login(self):
|
||||
# Click to hide the toolbar
|
||||
# Could be removed for localdev so it may fail
|
||||
self.selenium.find_element_by_id("djHideToolBarButton").click()
|
||||
self.find_element(*self._sign_in_locator).click()
|
||||
from pages.desktop.login import Login
|
||||
|
||||
return Login(self.selenium, self.page.base_url)
|
||||
|
||||
def register(self):
|
||||
# Click to hide the toolbar
|
||||
# Could be removed for localdev so it may fail
|
||||
self.selenium.find_element_by_id("djHideToolBarButton").click()
|
||||
self.find_element(*self._register_link_locator).click()
|
||||
|
||||
def click_sign_out(self):
|
||||
self.find_element(*self._sign_out_locator).click()
|
||||
from pages.desktop.devhub import DevHub
|
||||
|
||||
devhub = DevHub(self.selenium, self.page.base_url)
|
||||
return devhub.wait_for_page_to_load()
|
|
@ -1,26 +0,0 @@
|
|||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class DevHubAgreement(Base):
|
||||
|
||||
_accept_button_locator = (By.ID, 'accept-agreement')
|
||||
_addon_agreement_links_locator = (By.CLASS_NAME, 'agreement-links')
|
||||
_distribution_agreement_locator = (By.ID, 'id_distribution_agreement')
|
||||
_review_policy_locator = (By.ID, 'id_review_policy')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(
|
||||
*self._addon_agreement_links_locator
|
||||
)
|
||||
)
|
||||
return self
|
||||
|
||||
def accept_agreement(self):
|
||||
self.selenium.find_element(
|
||||
*self._distribution_agreement_locator
|
||||
).click()
|
||||
self.selenium.find_element(*self._review_policy_locator).click()
|
||||
self.selenium.find_element(*self._accept_button_locator).click()
|
|
@ -1,24 +0,0 @@
|
|||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class DevHubSource(Base):
|
||||
|
||||
_no_souce_code_locator = (By.ID, "id_has_source_1")
|
||||
_submit_source_locator = (By.ID, 'submit-source')
|
||||
_submission_button_locator = (
|
||||
By.CSS_SELECTOR, ".submission-buttons > button:nth-child(1)"
|
||||
)
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(
|
||||
*self._submit_source_locator
|
||||
)
|
||||
)
|
||||
return self
|
||||
|
||||
def dont_submit_source_code(self):
|
||||
self.selenium.find_element(*self._no_souce_code_locator).click()
|
||||
self.selenium.find_element(*self._submission_button_locator).click()
|
|
@ -1,52 +0,0 @@
|
|||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class DevhubSubmission(Base):
|
||||
"""Devhub submission page legacy view.
|
||||
|
||||
This will fill and submit an addon.
|
||||
|
||||
"""
|
||||
_name_locator = (By.ID, 'id_name')
|
||||
_summary_locator = (By.ID, 'id_summary_0')
|
||||
_license_btn_locator = (By.ID, 'id_license-builtin_0')
|
||||
_submit_btn_locator = (
|
||||
By.CSS_SELECTOR, '.submission-buttons > button:nth-child(1)'
|
||||
)
|
||||
_appearance_categories_locator = (By.ID, 'id_form-0-categories_0')
|
||||
_bookmarks_categories_locator = (By.ID, 'id_form-0-categories_1')
|
||||
_edit_submission_btn_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'.addon-submission-process > p:nth-child(7) > a:nth-child(1)'
|
||||
)
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(*self._name_locator)
|
||||
)
|
||||
return self
|
||||
|
||||
def fill_addon_submission_form(self):
|
||||
"""Fill addon submission form.
|
||||
|
||||
Currently there is a prefilled suggested name, specificying your own
|
||||
name will just be added to the end of the current name. The default
|
||||
name is "Ui-Test-Devhub-ext".
|
||||
|
||||
"""
|
||||
self.find_element(*self._summary_locator).send_keys('Words go here')
|
||||
self.find_element(*self._appearance_categories_locator).click()
|
||||
self.find_element(*self._bookmarks_categories_locator).click()
|
||||
self.find_element(*self._license_btn_locator).click()
|
||||
self.find_element(*self._submit_btn_locator).click()
|
||||
self.wait.until(
|
||||
lambda _: self.selenium.find_element(
|
||||
*self._edit_submission_btn_locator
|
||||
).is_displayed()
|
||||
)
|
||||
self.selenium.find_element(*self._edit_submission_btn_locator).click()
|
||||
from pages.desktop.manage_submissions import ManageSubmissions
|
||||
subs = ManageSubmissions(self.selenium, self.base_url)
|
||||
return subs.wait_for_page_to_load()
|
|
@ -1,30 +0,0 @@
|
|||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class EditAddon(Base):
|
||||
"""Edit page for a specific addon.
|
||||
|
||||
This page is the edit page for an addon that has already
|
||||
been approved.
|
||||
"""
|
||||
|
||||
_root_locator = (By.CLASS_NAME, 'section')
|
||||
_edit_addon_navbar_locator = (By.CLASS_NAME, 'edit-addon-nav')
|
||||
_addon_name_locator = (
|
||||
By.CSS_SELECTOR,
|
||||
'#main-wrapper > div:nth-child(1) >\
|
||||
header:nth-child(2) > h2:nth-child(2)',
|
||||
)
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _:
|
||||
self.is_element_displayed(*self._addon_name_locator)
|
||||
)
|
||||
return self
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._addon_name_locator).text
|
|
@ -1,24 +0,0 @@
|
|||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class EditTheme(Base):
|
||||
"""Edit page for a specific addon.
|
||||
|
||||
This page is the edit page for a theme that has already
|
||||
been approved.
|
||||
"""
|
||||
|
||||
_root_locator = (By.CLASS_NAME, 'section')
|
||||
_edit_addon_navbar_locator = (By.CLASS_NAME, 'edit-addon-nav')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(
|
||||
*self._edit_addon_navbar_locator))
|
||||
return self
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
pass
|
|
@ -1,63 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class Extensions(Base):
|
||||
|
||||
URL_TEMPLATE = 'extensions/'
|
||||
|
||||
_featured_addons_locator = (By.CLASS_NAME, 'FeaturedAddons')
|
||||
_top_rated_locator = (By.CLASS_NAME, 'HighlyRatedAddons')
|
||||
_title_locator = (By.CLASS_NAME, 'LandingPage-addonType-name')
|
||||
_trending_addons_locator = (By.CLASS_NAME, 'TrendingAddons')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(*self._title_locator))
|
||||
return self
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self.find_element(*self._title_locator).text
|
||||
|
||||
@property
|
||||
def extension_header(self):
|
||||
return self.ExtensionHeader(self)
|
||||
|
||||
@property
|
||||
def featured_extensions(self):
|
||||
items = self.find_elements(*self._featured_addons_locator)
|
||||
return [self.ExtensionDetail(self, el) for el in items]
|
||||
|
||||
@property
|
||||
def categories(self):
|
||||
from regions.desktop.categories import Categories
|
||||
return Categories(self)
|
||||
|
||||
class ExtensionHeader(Region):
|
||||
_root_locator = (By.CLASS_NAME, 'Category')
|
||||
_header_locator = (By.CLASS_NAME, 'CategoryHeader')
|
||||
_category_name_locator = (By.CLASS_NAME, 'CategoryHeader-name')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._category_name_locator).text
|
||||
|
||||
class ExtensionsList(Region):
|
||||
|
||||
_extensions_locator = (By.CLASS_NAME, 'SearchResult')
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
items = self.find_elements(*self._extensions_locator)
|
||||
return [self.ExtensionDetail(self.page, el) for el in items]
|
||||
|
||||
class ExtensionDetail(Region):
|
||||
|
||||
_extension_name_locator = (By.CLASS_NAME, 'SearchResult-name')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._extension_name_locator).text
|
|
@ -1,134 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class Home(Base):
|
||||
"""Addons Home page"""
|
||||
|
||||
_extensions_category_locator = (By.CLASS_NAME, 'Home-CuratedCollections')
|
||||
_featured_extensions_locator = (
|
||||
By.CLASS_NAME, 'Home-RecommendedExtensions'
|
||||
)
|
||||
_featured_themes_locator = (By.CLASS_NAME, 'Home-FeaturedThemes')
|
||||
_hero_locator = (By.CLASS_NAME, 'HeroRecommendation')
|
||||
_popular_extensions_locator = (By.CLASS_NAME, 'Home-PopularExtensions')
|
||||
_popular_themes_locator = (By.CLASS_NAME, 'Home-PopularThemes')
|
||||
_themes_category_locator = (By.CLASS_NAME, 'Home-CuratedThemes')
|
||||
_toprated_themes_locator = (By.CLASS_NAME, 'Home-TopRatedThemes')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(*self._hero_locator)
|
||||
)
|
||||
return self
|
||||
|
||||
@property
|
||||
def hero_banner(self):
|
||||
return self.find_element(*self._hero_locator)
|
||||
|
||||
@property
|
||||
def popular_extensions(self):
|
||||
el = self.find_element(*self._popular_extensions_locator)
|
||||
return self.Extensions(self, el)
|
||||
|
||||
@property
|
||||
def featured_extensions(self):
|
||||
el = self.find_element(*self._featured_extensions_locator)
|
||||
return self.Extensions(self, el)
|
||||
|
||||
@property
|
||||
def featured_themes(self):
|
||||
el = self.find_element(*self._featured_themes_locator)
|
||||
return self.Themes(self, el)
|
||||
|
||||
@property
|
||||
def popular_themes(self):
|
||||
el = self.find_element(*self._popular_themes_locator)
|
||||
return self.Themes(self, el)
|
||||
|
||||
@property
|
||||
def toprated_themes(self):
|
||||
el = self.find_element(*self._toprated_themes_locator)
|
||||
return self.Themes(self, el)
|
||||
|
||||
@property
|
||||
def extension_category(self):
|
||||
el = self.find_element(*self._extensions_category_locator)
|
||||
return self.Category(self, el)
|
||||
|
||||
@property
|
||||
def theme_category(self):
|
||||
el = self.find_element(*self._themes_category_locator)
|
||||
return self.Category(self, el)
|
||||
|
||||
class Category(Region):
|
||||
_extensions_locator = (By.CLASS_NAME, 'Home-SubjectShelf-list-item')
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
items = self.find_elements(*self._extensions_locator)
|
||||
return [self.CategoryDetail(self.page, el) for el in items]
|
||||
|
||||
class CategoryDetail(Region):
|
||||
_extension_link_locator = (By.CLASS_NAME, 'Home-SubjectShelf-link')
|
||||
_extension_name_locator = (
|
||||
By.CSS_SELECTOR, '.Home-SubjectShelf-link span')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._extension_name_locator).text
|
||||
|
||||
def click(self):
|
||||
self.root.click()
|
||||
from pages.desktop.extensions import Extensions
|
||||
return Extensions(self.selenium, self.page.base_url)
|
||||
|
||||
class Extensions(Region):
|
||||
_browse_all_locator = (By.CSS_SELECTOR, '.Card-footer-link > a')
|
||||
_extensions_locator = (By.CLASS_NAME, 'SearchResult')
|
||||
_extension_card_locator = (By.CSS_SELECTOR, '.Home-category-li')
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
items = self.find_elements(*self._extensions_locator)
|
||||
return [Home.ExtensionsList(self.page, el) for el in items]
|
||||
|
||||
@property
|
||||
def browse_all(self):
|
||||
self.find_element(*self._browse_all_locator).click()
|
||||
from pages.desktop.search import Search
|
||||
search = Search(self.selenium, self.page.base_url)
|
||||
return search.wait_for_page_to_load()
|
||||
|
||||
class Themes(Region):
|
||||
_browse_all_locator = (By.CSS_SELECTOR, '.Card-footer-link > a')
|
||||
_themes_locator = (By.CLASS_NAME, 'SearchResult--theme')
|
||||
_theme_card_locator = (By.CSS_SELECTOR, '.Home-category-li')
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
items = self.find_elements(*self._themes_locator)
|
||||
return [Home.ExtensionsList(self.page, el) for el in items]
|
||||
|
||||
@property
|
||||
def browse_all(self):
|
||||
self.find_element(*self._browse_all_locator).click()
|
||||
from pages.desktop.search import Search
|
||||
search = Search(self.selenium, self.page.base_url)
|
||||
return search.wait_for_page_to_load()
|
||||
|
||||
class ExtensionsList(Region):
|
||||
|
||||
_extension_link_locator = (By.CLASS_NAME, 'SearchResult-link')
|
||||
_extension_name_locator = (By.CLASS_NAME, 'SearchResult-name')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._extension_name_locator).text
|
||||
|
||||
def click(self):
|
||||
self.find_element(*self._extension_link_locator).click()
|
||||
from pages.desktop.extensions import Extensions
|
||||
return Extensions(self.selenium, self.page.base_url)
|
|
@ -1,13 +0,0 @@
|
|||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class Login(Base):
|
||||
|
||||
_email_locator = (By.ID, 'id_username')
|
||||
_continue_locator = (By.CSS_SELECTOR, '#normal-login .login-source-button')
|
||||
|
||||
def login(self, email, password):
|
||||
from fxapom.pages.sign_in import SignIn
|
||||
SignIn(self.selenium).sign_in(email, password)
|
|
@ -1,31 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class ManageSubmissions(Base):
|
||||
"""Submission management legacy page."""
|
||||
|
||||
_root_locator = (By.CSS_SELECTOR, '.listing .island')
|
||||
_page_title_locator = (By.CSS_SELECTOR, '.primary .hero > h2')
|
||||
_addon_submissions_locator = (By.CLASS_NAME, 'addon')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(*self._page_title_locator)
|
||||
)
|
||||
return self
|
||||
|
||||
@property
|
||||
def addons(self):
|
||||
els = self.find_elements(*self._addon_submissions_locator)
|
||||
return [self.AddonDetail(self, el) for el in els]
|
||||
|
||||
class AddonDetail(Region):
|
||||
|
||||
_addon_name_locator = (By.CSS_SELECTOR, '.info > h3')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._addon_name_locator).text
|
|
@ -1,81 +0,0 @@
|
|||
from pypom import Page, Region
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as expected
|
||||
|
||||
|
||||
class Search(Page):
|
||||
|
||||
_search_box_locator = (By.CLASS_NAME, 'AutoSearchInput-query')
|
||||
_submit_button_locator = (By.CLASS_NAME, 'AutoSearchInput-submit-button')
|
||||
_search_filters_sort_locator = (By.ID, 'SearchFilters-Sort')
|
||||
_search_filters_type_locator = (By.ID, 'SearchFilters-AddonType')
|
||||
_search_filters_os_locator = (By.ID, 'SearchFilters-OperatingSystem')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
expected.invisibility_of_element_located(
|
||||
(By.CLASS_NAME, 'LoadingText')))
|
||||
return self
|
||||
|
||||
@property
|
||||
def result_list(self):
|
||||
return self.SearchResultList(self)
|
||||
|
||||
def filter_by_sort(self, value):
|
||||
self.find_element(*self._search_filters_sort_locator).click()
|
||||
self.find_element(*self._search_filters_sort_locator).send_keys(value)
|
||||
|
||||
def filter_by_type(self, value):
|
||||
self.find_element(*self._search_filters_type_locator).click()
|
||||
self.find_element(*self._search_filters_type_locator).send_keys(value)
|
||||
|
||||
def filter_by_os(self, value):
|
||||
self.find_element(*self._search_filters_os_locator).click()
|
||||
self.find_element(*self._search_filters_os_locator).send_keys(value)
|
||||
|
||||
class SearchResultList(Region):
|
||||
|
||||
_result_locator = (By.CLASS_NAME, 'SearchResult')
|
||||
_theme_locator = (By.CLASS_NAME, 'SearchResult--theme')
|
||||
_extension_locator = (By.CLASS_NAME, 'SearchResult-name')
|
||||
|
||||
@property
|
||||
def extensions(self):
|
||||
items = self.find_elements(*self._result_locator)
|
||||
return [self.ResultListItems(self, el) for el in items]
|
||||
|
||||
@property
|
||||
def themes(self):
|
||||
items = self.find_elements(*self._theme_locator)
|
||||
return [self.ResultListItems(self, el) for el in items]
|
||||
|
||||
class ResultListItems(Region):
|
||||
|
||||
_rating_locator = (By.CSS_SELECTOR, '.Rating--small')
|
||||
_search_item_name_locator = (By.CSS_SELECTOR,
|
||||
'.SearchResult-contents > h2')
|
||||
_users_locator = (By.CLASS_NAME, 'SearchResult-users-text')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._search_item_name_locator).text
|
||||
|
||||
def link(self):
|
||||
self.find_element(*self._search_item_name_locator).click()
|
||||
from pages.desktop.details import Detail
|
||||
|
||||
detail_page = Detail(self.selenium, self.page.base_url)
|
||||
return detail_page.wait_for_page_to_load()
|
||||
|
||||
@property
|
||||
def users(self):
|
||||
users = self.find_element(*self._users_locator).text
|
||||
return int(
|
||||
users.split()[0].replace(',', '').replace('users', ''))
|
||||
|
||||
@property
|
||||
def rating(self):
|
||||
"""Returns the rating"""
|
||||
rating = self.find_element(
|
||||
*self._rating_locator).get_property('title')
|
||||
return int(rating.split()[1])
|
|
@ -1,20 +0,0 @@
|
|||
from selenium.webdriver.common.by import By
|
||||
|
||||
from pages.desktop.base import Base
|
||||
|
||||
|
||||
class Themes(Base):
|
||||
|
||||
URL_TEMPLATE = 'themes/'
|
||||
|
||||
_browse_all_locator = (By.CSS_SELECTOR, '.Card-footer-link > a')
|
||||
_title_locator = (By.CLASS_NAME, 'LandingPage-addonType-name')
|
||||
|
||||
def wait_for_page_to_load(self):
|
||||
self.wait.until(
|
||||
lambda _: self.is_element_displayed(*self._title_locator))
|
||||
return self.find_element(*self._title_locator)
|
||||
|
||||
@property
|
||||
def browse_all(self):
|
||||
self.find_element(*self._browse_all_locator).click()
|
|
@ -1,25 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
|
||||
class Categories(Region):
|
||||
|
||||
_root_locator = (By.CLASS_NAME, 'Categories')
|
||||
_categories_locator = (By.CLASS_NAME, 'Categories-item')
|
||||
|
||||
@property
|
||||
def category_list(self):
|
||||
items = self.find_elements(*self._categories_locator)
|
||||
return [self.CategoryList(self, el) for el in items]
|
||||
|
||||
class CategoryList(Region):
|
||||
_name_locator = (By.CLASS_NAME, 'Categories-link')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._name_locator).text
|
||||
|
||||
def click(self):
|
||||
self.root.click()
|
||||
from pages.desktop.category import Category
|
||||
return Category(self.selenium, self.page)
|
|
@ -1,34 +0,0 @@
|
|||
from pypom import Region
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as expected
|
||||
|
||||
|
||||
class Search(Region):
|
||||
|
||||
_search_results_locator = (By.CLASS_NAME, 'SearchForm-suggestions-item')
|
||||
|
||||
def wait_for_region_to_load(self):
|
||||
self.wait.until(
|
||||
expected.invisibility_of_element_located(
|
||||
(By.CLASS_NAME, 'LoadingText')))
|
||||
return self
|
||||
|
||||
@property
|
||||
def result_list(self):
|
||||
items = self.find_elements(*self._search_results_locator)
|
||||
return [self.SearchResultList(self.page, el) for el in items]
|
||||
|
||||
class SearchResultList(Region):
|
||||
|
||||
_search_item_name_locator = (By.CLASS_NAME, 'Suggestion-name')
|
||||
_search_item_link_locator = (By.CLASS_NAME, 'Suggestion')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._search_item_name_locator).text
|
||||
|
||||
def link(self):
|
||||
self.find_element(*self._search_item_link_locator).click()
|
||||
from pages.desktop.detail import Detail
|
||||
return Detail(
|
||||
self.selenium, self.page.base_url).wait_for_page_to_load()
|
|
@ -1,15 +0,0 @@
|
|||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
# Script for running the auto approve command every 10 seconds for 5 minutes.
|
||||
def approve_addons():
|
||||
start_time = time.time()
|
||||
# run for 5 minutes max
|
||||
while time.time() != start_time + 300:
|
||||
subprocess.call(['python', 'manage.py', 'auto_approve'])
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
approve_addons()
|
|
@ -1,9 +0,0 @@
|
|||
import uuid
|
||||
|
||||
|
||||
def create_fxa_email():
|
||||
print(f"uitest-{uuid.uuid1()}@restmail.net")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_fxa_email()
|
|
@ -1,5 +0,0 @@
|
|||
[tool:pytest]
|
||||
addopts = -vs --tb=long --showlocals --html=ui-test.html --self-contained-html
|
||||
sensitive_url = mozilla\.(com|org)
|
||||
xfail_strict = true
|
||||
DJANGO_SETTINGS_MODULE = settings
|
|
@ -1,90 +0,0 @@
|
|||
import time
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
|
||||
from pages.desktop.devhub import DevHub
|
||||
from pages.desktop.details import Detail
|
||||
|
||||
|
||||
@pytest.mark.fxa_login
|
||||
@pytest.mark.desktop_only
|
||||
@pytest.mark.nondestructive
|
||||
@pytest.mark.allow_external_http_requests
|
||||
def test_devhub_home_loads_addons(base_url, selenium, devhub_login):
|
||||
"""Test devhub home loads correct number of addons listed."""
|
||||
devhub = devhub_login
|
||||
r = requests.get('{}/api/v4/accounts/account/uitest/'.format(base_url))
|
||||
author_addons = r.json()['num_addons_listed']
|
||||
assert len(devhub.addons_list) == author_addons
|
||||
|
||||
|
||||
@pytest.mark.fxa_login
|
||||
@pytest.mark.desktop_only
|
||||
@pytest.mark.nondestructive
|
||||
@pytest.mark.allow_external_http_requests
|
||||
def test_devhub_addon_edit_link_works(base_url, selenium, devhub_login):
|
||||
"""Test addon edit link returns edit page."""
|
||||
devhub = devhub_login
|
||||
addon_name = devhub.addons_list[0].name
|
||||
addon_editor = devhub.addons_list[0].edit()
|
||||
assert addon_name == addon_editor.name
|
||||
|
||||
|
||||
@pytest.mark.fxa_login
|
||||
@pytest.mark.desktop_only
|
||||
@pytest.mark.nondestructive
|
||||
@pytest.mark.allow_external_http_requests
|
||||
@pytest.mark.withoutresponses
|
||||
def test_devhub_logout(base_url, selenium, devhub_login):
|
||||
"""Logging out from devhub."""
|
||||
assert devhub_login.logged_in
|
||||
devhub = devhub_login.header.click_sign_out()
|
||||
assert not devhub.logged_in
|
||||
|
||||
|
||||
@pytest.mark.desktop_only
|
||||
@pytest.mark.nondestructive
|
||||
def test_devhub_register(base_url, selenium):
|
||||
"""Test register link loads register page."""
|
||||
selenium.get('{}/developers'.format(base_url))
|
||||
devhub = DevHub(selenium, base_url)
|
||||
assert not devhub.logged_in
|
||||
devhub.header.register()
|
||||
assert 'signup' in selenium.current_url
|
||||
|
||||
|
||||
@pytest.mark.fxa_login
|
||||
@pytest.mark.desktop_only
|
||||
@pytest.mark.nondestructive
|
||||
@pytest.mark.allow_external_http_requests
|
||||
def test_devhub_addon_upload_approve_install(
|
||||
base_url, selenium, devhub_upload, firefox, firefox_notifications):
|
||||
"""Test uploading an addon via devhub."""
|
||||
'ui-test-addon-2' in devhub_upload.addons[-1].name
|
||||
# We have to wait for approval
|
||||
time.sleep(15)
|
||||
page_loaded = False
|
||||
while page_loaded is not True:
|
||||
try:
|
||||
selenium.get('{}/addon/ui-test_devhub_ext/'.format(base_url))
|
||||
addon = Detail(selenium, base_url)
|
||||
'UI-Test_devhub_ext' in addon.name
|
||||
except NoSuchElementException:
|
||||
pass
|
||||
except Exception:
|
||||
raise Exception
|
||||
else:
|
||||
page_loaded = True
|
||||
return page_loaded
|
||||
addon.install()
|
||||
firefox.browser.wait_for_notification(
|
||||
firefox_notifications.AddOnInstallBlocked
|
||||
).allow()
|
||||
firefox.browser.wait_for_notification(
|
||||
firefox_notifications.AddOnInstallConfirmation
|
||||
).install()
|
||||
firefox.browser.wait_for_notification(
|
||||
firefox_notifications.AddOnInstallComplete
|
||||
).close()
|
|
@ -1,100 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from pages.desktop.extensions import Extensions
|
||||
from pages.desktop.home import Home
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_extensions_section_load_correctly(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
ext_page = page.header.click_extensions()
|
||||
assert 'Extensions' in ext_page.title
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_explore_section_loads(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
page.header.click_explore()
|
||||
assert 'firefox/' in selenium.current_url
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_themes_section_loads(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
themes_page = page.header.click_themes()
|
||||
assert 'Themes' in themes_page.text
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_browse_all_button_loads_correct_page(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
page.featured_extensions.browse_all
|
||||
assert 'type=extension' in selenium.current_url
|
||||
|
||||
|
||||
@pytest.mark.desktop_only
|
||||
@pytest.mark.nondestructive
|
||||
def test_category_section_loads_correct_category(base_url, selenium):
|
||||
page = Extensions(selenium, base_url).open()
|
||||
item = page.categories.category_list[0]
|
||||
name = item.name
|
||||
category = item.click()
|
||||
assert name in category.header.name
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_title_routes_to_home(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
home = page.header.click_title()
|
||||
assert home.hero_banner.is_displayed()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'i, page_url',
|
||||
enumerate(['language-tools', 'search-tools', 'android']))
|
||||
@pytest.mark.nondestructive
|
||||
def test_more_dropdown_navigates_correctly(base_url, selenium, i, page_url):
|
||||
page = Home(selenium, base_url).open()
|
||||
page.header.more_menu(item=i)
|
||||
assert page_url in selenium.current_url
|
||||
|
||||
|
||||
@pytest.mark.desktop_only
|
||||
@pytest.mark.parametrize(
|
||||
'i, links',
|
||||
enumerate([
|
||||
'about',
|
||||
'blog.mozilla.org',
|
||||
'extensionworkshop',
|
||||
'developers',
|
||||
'AMO/Policy',
|
||||
'discourse',
|
||||
'#Contact_us',
|
||||
'review_guide',
|
||||
'status',
|
||||
])
|
||||
)
|
||||
@pytest.mark.nondestructive
|
||||
def test_add_ons_footer_links(base_url, selenium, i, links):
|
||||
page = Home(selenium, base_url).open()
|
||||
page.footer.addon_links[i].click()
|
||||
assert links in selenium.current_url
|
||||
|
||||
|
||||
@pytest.mark.desktop_only
|
||||
@pytest.mark.parametrize(
|
||||
'i, links',
|
||||
enumerate([
|
||||
'firefox/new',
|
||||
'firefox/mobile',
|
||||
'firefox/mobile',
|
||||
'firefox/mobile',
|
||||
'firefox',
|
||||
'firefox/channel/desktop',
|
||||
])
|
||||
)
|
||||
@pytest.mark.nondestructive
|
||||
def test_firefox_footer_links(base_url, selenium, i, links):
|
||||
page = Home(selenium, base_url).open()
|
||||
page.footer.firefox_links[i].click()
|
||||
assert links in selenium.current_url
|
|
@ -1,23 +0,0 @@
|
|||
import pytest
|
||||
|
||||
|
||||
from pages.desktop.details import Detail
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_addon_install(base_url, selenium, firefox, firefox_notifications):
|
||||
"""Test that navigates to an addon and installs it."""
|
||||
selenium.get('{}/addon/ui-test-install'.format(base_url))
|
||||
addon = Detail(selenium, base_url)
|
||||
assert 'Ui-Addon-Install' in addon.name
|
||||
assert addon.is_compatible
|
||||
addon.install()
|
||||
firefox.browser.wait_for_notification(
|
||||
firefox_notifications.AddOnInstallBlocked
|
||||
).allow()
|
||||
firefox.browser.wait_for_notification(
|
||||
firefox_notifications.AddOnInstallConfirmation
|
||||
).install()
|
||||
firefox.browser.wait_for_notification(
|
||||
firefox_notifications.AddOnInstallComplete
|
||||
).close()
|
|
@ -1,25 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from pages.desktop.home import Home
|
||||
|
||||
|
||||
@pytest.mark.fxa_login
|
||||
@pytest.mark.skip("Addons frontend login not working")
|
||||
@pytest.mark.allow_external_http_requests
|
||||
def test_login(base_url, selenium, fxa_account):
|
||||
"""User can login"""
|
||||
page = Home(selenium, base_url).open()
|
||||
assert not page.logged_in
|
||||
page.login(fxa_account.email, fxa_account.password)
|
||||
assert page.logged_in
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason='https://bugzilla.mozilla.org/show_bug.cgi?id=1453779')
|
||||
@pytest.mark.allow_external_http_requests
|
||||
def test_logout(base_url, selenium, user):
|
||||
"""User can logout"""
|
||||
page = Home(selenium, base_url).open()
|
||||
page.login(user['email'], user['password'])
|
||||
page.logout()
|
||||
assert not page.logged_in
|
|
@ -1,125 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
from pages.desktop.home import Home
|
||||
from pages.desktop.search import Search
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_search_loads_and_navigates_to_correct_page(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
addon_name = page.featured_extensions.list[0].name
|
||||
search = page.search.search_for(addon_name)
|
||||
search_name = search.result_list.extensions[0].name
|
||||
assert addon_name in search_name
|
||||
assert search_name in search.result_list.extensions[0].name
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_search_loads_correct_results(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
addon_name = page.featured_extensions.list[0].name
|
||||
items = page.search.search_for(addon_name)
|
||||
assert addon_name in items.result_list.extensions[0].name
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_legacy_extensions_do_not_load(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
term = 'Video Download Manager'
|
||||
items = page.search.search_for(term)
|
||||
for item in items.result_list.extensions:
|
||||
assert term not in item.name
|
||||
|
||||
|
||||
@pytest.mark.xfail(strict=False)
|
||||
@pytest.mark.parametrize('category, sort_attr', [
|
||||
['Most Users', 'users'],
|
||||
['Top Rated', 'rating']])
|
||||
def test_sorting_by(base_url, selenium, category, sort_attr):
|
||||
"""Test searching for an addon and sorting."""
|
||||
Home(selenium, base_url).open()
|
||||
addon_name = 'Ui-Addon'
|
||||
selenium.get('{}/search/?&q={}&sort={}'.format(
|
||||
base_url, addon_name, sort_attr)
|
||||
)
|
||||
search_page = Search(selenium, base_url)
|
||||
results = [getattr(i, sort_attr)
|
||||
for i in search_page.result_list.extensions]
|
||||
assert sorted(results, reverse=True) == results
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_incompative_extensions_show_as_incompatible(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
term = 'Ui-Addon-Android'
|
||||
results = page.search.search_for(term)
|
||||
for item in results.result_list.extensions:
|
||||
if term == item.name:
|
||||
detail_page = item.click()
|
||||
assert detail_page.is_compatible is False
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_search_suggestion_term_is_higher(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
term = 'Ui-Addon-Install'
|
||||
suggestions = page.search.search_for(term, execute=False)
|
||||
assert suggestions[0].name == term
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_special_chars_dont_break_suggestions(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
term = 'Ui-Addon'
|
||||
special_chars_term = f'{term}%ç√®å'
|
||||
suggestions = page.search.search_for(special_chars_term, execute=False)
|
||||
results = [item.name for item in suggestions]
|
||||
assert term in results
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_capitalization_has_same_suggestions(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
term = 'Ui-Addon-Install'
|
||||
suggestions = page.search.search_for(term.capitalize(), execute=False)
|
||||
# Sleep to let autocomplete update.
|
||||
time.sleep(2)
|
||||
assert term == suggestions[0].name
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_esc_key_closes_suggestion_list(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
term = 'Ui-Addon-Install'
|
||||
page.search.search_for(term, execute=False)
|
||||
action = ActionChains(selenium)
|
||||
# Send ESC key to browser
|
||||
action.send_keys(Keys.ESCAPE).perform()
|
||||
with pytest.raises(NoSuchElementException):
|
||||
selenium.find_element_by_css_selector(
|
||||
'AutoSearchInput-suggestions-list')
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_long_terms_dont_break_suggestions(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
term = 'Ui-Addon'
|
||||
additional_term = ' 123456789'
|
||||
page.search.search_for(term, execute=False)
|
||||
suggestions = page.search.search_for(additional_term, execute=False)
|
||||
# Sleep to let autocomplete update.
|
||||
time.sleep(2)
|
||||
assert term in suggestions[0].name
|
||||
|
||||
|
||||
@pytest.mark.nondestructive
|
||||
def test_blank_search_loads_results_page(base_url, selenium):
|
||||
page = Home(selenium, base_url).open()
|
||||
results = page.search.search_for('', execute=True)
|
||||
assert 'Ui-Addon' in results.result_list.extensions[0].name
|
9
tox.ini
9
tox.ini
|
@ -17,7 +17,7 @@ whitelist_externals =
|
|||
[testenv:es]
|
||||
commands =
|
||||
make install_python_test_dependencies
|
||||
pytest -m "es_tests and not needs_locales_compilation and not static_assets" --ignore=tests/ui/ -v src/olympia/ {posargs}
|
||||
pytest -m "es_tests and not needs_locales_compilation and not static_assets" -v src/olympia/ {posargs}
|
||||
|
||||
[testenv:addons-versions-files-ratings]
|
||||
commands =
|
||||
|
@ -57,16 +57,11 @@ commands =
|
|||
--ignore src/olympia/zadmin \
|
||||
{posargs}
|
||||
|
||||
[testenv:ui-tests]
|
||||
commands =
|
||||
make -f Makefile-docker update_deps
|
||||
pip install --no-deps -r requirements/uitests.txt
|
||||
pytest --driver Firefox tests/ui/ {posargs}
|
||||
|
||||
[testenv:assets]
|
||||
commands =
|
||||
make update_deps
|
||||
pytest -m "static_assets" --ignore=tests/ui/ -v src/olympia/ {posargs}
|
||||
pytest -m "static_assets" -v src/olympia/ {posargs}
|
||||
|
||||
[testenv:codestyle]
|
||||
recreate = True
|
||||
|
|
Загрузка…
Ссылка в новой задаче