From a9fd5c53c63e55c507bfa4760db23282b8f4dbb9 Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Sat, 27 Oct 2018 12:11:18 +0100 Subject: [PATCH] Add selenium test for admin list page Part of #30 --- Makefile | 1 + bdd/bdd/__init__.py | 0 bdd/bdd/admin.py | 7 ++++ bdd/bdd/migrations/0001_initial.py | 25 +++++++++++++ bdd/bdd/migrations/__init__.py | 0 bdd/bdd/models.py | 7 ++++ bdd/bdd/settings.py | 57 ++++++++++++++++++++++++++++++ bdd/bdd/urls.py | 7 ++++ bdd/manage.py | 15 ++++++++ features/browser.py | 9 +++++ features/environment.py | 11 ++++++ features/order-list.feature | 15 ++++++++ features/pages.py | 22 ++++++++++++ features/steps/steps.py | 43 ++++++++++++++++++++++ requirements.txt | 4 +++ setup.cfg | 4 ++- 16 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 bdd/bdd/__init__.py create mode 100644 bdd/bdd/admin.py create mode 100644 bdd/bdd/migrations/0001_initial.py create mode 100644 bdd/bdd/migrations/__init__.py create mode 100644 bdd/bdd/models.py create mode 100644 bdd/bdd/settings.py create mode 100644 bdd/bdd/urls.py create mode 100755 bdd/manage.py create mode 100644 features/browser.py create mode 100644 features/environment.py create mode 100644 features/order-list.feature create mode 100644 features/pages.py create mode 100644 features/steps/steps.py diff --git a/Makefile b/Makefile index 7ea8161..b8fd774 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ help: test: @coverage run orderable/tests/run.py @coverage report -m + @bdd/manage.py behave @flake8 release: diff --git a/bdd/bdd/__init__.py b/bdd/bdd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bdd/bdd/admin.py b/bdd/bdd/admin.py new file mode 100644 index 0000000..b3cffdb --- /dev/null +++ b/bdd/bdd/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from orderable.admin import OrderableAdmin +from .models import Item + + +admin.site.register(Item, OrderableAdmin) diff --git a/bdd/bdd/migrations/0001_initial.py b/bdd/bdd/migrations/0001_initial.py new file mode 100644 index 0000000..9196ace --- /dev/null +++ b/bdd/bdd/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1.2 on 2018-10-26 21:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Item', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sort_order', models.IntegerField(blank=True, db_index=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + ), + ] diff --git a/bdd/bdd/migrations/__init__.py b/bdd/bdd/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bdd/bdd/models.py b/bdd/bdd/models.py new file mode 100644 index 0000000..ca47f3f --- /dev/null +++ b/bdd/bdd/models.py @@ -0,0 +1,7 @@ +from django.db import models + +from orderable.models import Orderable + + +class Item(Orderable): + pass diff --git a/bdd/bdd/settings.py b/bdd/bdd/settings.py new file mode 100644 index 0000000..172b597 --- /dev/null +++ b/bdd/bdd/settings.py @@ -0,0 +1,57 @@ +import os + + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +SECRET_KEY = 'r+n-oywnjf7&b*#(!@zx-wa4j=4_yy7a%j-lep&q8nx6^=lwf5' +ROOT_URLCONF = 'bdd.urls' +STATIC_URL = '/static/' +DATABASES = {'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'orderable', + 'HOST': 'localhost' +}} + +INSTALLED_APPS = [ + # Project. + 'bdd', + + # 3rd party. + 'behave_django', + 'orderable', + + # Django. + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] diff --git a/bdd/bdd/urls.py b/bdd/bdd/urls.py new file mode 100644 index 0000000..09e4254 --- /dev/null +++ b/bdd/bdd/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url +from django.contrib import admin + + +urlpatterns = [ + url('^admin/', admin.site.urls), +] diff --git a/bdd/manage.py b/bdd/manage.py new file mode 100755 index 0000000..7587891 --- /dev/null +++ b/bdd/manage.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == '__main__': + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bdd.settings') + try: + from django.core.management import execute_from_command_line + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + execute_from_command_line(sys.argv) diff --git a/features/browser.py b/features/browser.py new file mode 100644 index 0000000..016f417 --- /dev/null +++ b/features/browser.py @@ -0,0 +1,9 @@ +from selenium import webdriver + + +class Browser(object): + driver = webdriver.Chrome() + driver.implicitly_wait(5) + + def close(context): + context.driver.close() diff --git a/features/environment.py b/features/environment.py new file mode 100644 index 0000000..445fbdf --- /dev/null +++ b/features/environment.py @@ -0,0 +1,11 @@ +from browser import Browser +from pages import ItemListPage + + +def before_all(context): + context.browser = Browser() + context.item_list_page = ItemListPage() + + +def after_all(context): + context.browser.close() diff --git a/features/order-list.feature b/features/order-list.feature new file mode 100644 index 0000000..90a5828 --- /dev/null +++ b/features/order-list.feature @@ -0,0 +1,15 @@ +Feature: Ordering on the list page + + Scenario: Move an item to the end of the list + Given the following items: + | pk | + | 1 | + | 2 | + | 3 | + And we are on the item list page + When item 1 is moved to position 3 + Then the items should be ordered thus: + | pk | + | 2 | + | 3 | + | 1 | diff --git a/features/pages.py b/features/pages.py new file mode 100644 index 0000000..54ba3ae --- /dev/null +++ b/features/pages.py @@ -0,0 +1,22 @@ +from browser import Browser +from selenium.webdriver.common.action_chains import ActionChains + + +class ItemListPage(Browser): + def move_item(self, source, destination): + + template = '#neworder-{} .ui-sortable-handle' + find = self.driver.find_element_by_css_selector + offset = 2 if destination > source else -2 + ( + ActionChains(self.driver) + .click_and_hold(find(template.format(source))) + .move_to_element(find(template.format(destination))) + .move_by_offset(0, offset) + .release() + .perform() + ) + + def open(self, context): + url = context.get_url('admin:bdd_item_changelist') + self.driver.get(url) diff --git a/features/steps/steps.py b/features/steps/steps.py new file mode 100644 index 0000000..d9c7553 --- /dev/null +++ b/features/steps/steps.py @@ -0,0 +1,43 @@ +import time + +from behave import given, then, when +from django.contrib.auth.models import User +from seleniumlogin import force_login + +from bdd.models import Item + + +@given(u'the following items') +def set_up_items(context): + Item.objects.bulk_create([ + Item(pk=row['pk'], sort_order=i) + for i, row + in enumerate(context.table) + ]) + + +@given(u'we are on the item list page') +def go_to_admin_page(context): + user = User.objects.create_superuser( + email='user@example.com', + password='password', + username='myuser', + ) + force_login(user, context.browser.driver, context.test.live_server_url) + context.item_list_page.open(context) + + +@when(u'item {initial_position:d} is moved to position {new_position:d}') +def move_items(context, initial_position, new_position): + context.item_list_page.move_item(initial_position, new_position) + time.sleep(.1) + + +@then(u'the items should be ordered thus') +def check_item_order(context): + items = list(Item.objects.values_list('pk', flat=True)) + expected = [] + for row in context.table: + expected.append(int(row['pk'])) + + assert items == expected, (items, expected) diff --git a/requirements.txt b/requirements.txt index 84d043f..42f7248 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,9 @@ +behave==1.2.6 +behave_django==1.1.0 coverage==3.7.1 +django-selenium-login==1.0.2 flake8==3.6.0 flake8-import-order==0.18 hypothesis==2.0.0 psycopg2==2.7.4 +selenium==3.14.1 diff --git a/setup.cfg b/setup.cfg index d829475..426ffef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,9 @@ [wheel] universal = 1 [flake8] -application-import-names = orderable +application-import-names = orderable, bdd +exclude = + bdd/bdd/migrations import-order-style = google max-complexity = 10 max-line-length = 90