This commit is contained in:
dbxnr 2020-01-13 23:02:33 +00:00
Родитель 27f6f8fe9b
Коммит 0c845636a9
38 изменённых файлов: 2 добавлений и 1923 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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