mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2025-04-03 00:07:05 +00:00
qa: add support for Nextcloud 25
This commit is contained in:
parent
b4624b35eb
commit
49bcf7ba59
60
tests/lib/python/browser/NcContactsAutomation.py
Normal file
60
tests/lib/python/browser/NcContactsAutomation.py
Normal file
@ -0,0 +1,60 @@
|
||||
#####
|
||||
##### This file is part of Mail-in-a-Box-LDAP which is released under the
|
||||
##### terms of the GNU Affero General Public License as published by the
|
||||
##### Free Software Foundation, either version 3 of the License, or (at
|
||||
##### your option) any later version. See file LICENSE or go to
|
||||
##### https://github.com/downtownallday/mailinabox-ldap for full license
|
||||
##### details.
|
||||
#####
|
||||
|
||||
from selenium.common.exceptions import (
|
||||
NoSuchElementException,
|
||||
)
|
||||
|
||||
class NcContactsAutomation(object):
|
||||
def __init__(self, nc):
|
||||
''' `nc` is a NextcloudAutomation object '''
|
||||
self.nc = nc
|
||||
self.d = nc.d
|
||||
|
||||
def click_contact(self, contact):
|
||||
d = self.d
|
||||
d.say("Click contact %s", contact['email'])
|
||||
found = False
|
||||
# .list-item-content (nc 25+)
|
||||
# .option__details (nc <25)
|
||||
els = d.find_els('div.contacts-list div.list-item-content,div.option__details')
|
||||
d.say_verbose('found %s contacts' % len(els))
|
||||
for el in els:
|
||||
# .line-one (nc 25+)
|
||||
# .option__lineone (nc <25)
|
||||
fullname = el.find_el('.line-one,.option__lineone').content().strip()
|
||||
email = el.find_el('.line-two,.option__linetwo').content().strip()
|
||||
d.say_verbose('contact: "%s" <%s>', fullname, email)
|
||||
if fullname.lower() == "%s %s" % (contact['givenname'].lower(), contact['surname'].lower()) and email.lower() == contact['email'].lower():
|
||||
found = True
|
||||
el.click()
|
||||
break
|
||||
if not found: raise NoSuchElementException()
|
||||
|
||||
def wait_contact_loaded(self, secs=5):
|
||||
d = self.d
|
||||
d.say("Wait for contact to load")
|
||||
d.wait_for_el('section.contact-details', secs=secs)
|
||||
|
||||
def delete_current_contact(self):
|
||||
d = self.d
|
||||
d.say("Delete current contact")
|
||||
# Click ... menu
|
||||
d.find_el('.contact-header__actions button.action-item__menutoggle').click()
|
||||
# .v-popper__popper (nc 25+)
|
||||
# .popover (nc <25)
|
||||
el = d.wait_for_el(
|
||||
'.v-popper__popper,.popover',
|
||||
must_be_displayed=True,
|
||||
secs=2
|
||||
)
|
||||
# click "delete"
|
||||
# .delete-icon (nc 25+)
|
||||
# .icon-delete (nc <25)
|
||||
delete = el.find_el('span.delete-icon,span.icon-delete').click()
|
@ -10,6 +10,7 @@
|
||||
from selenium.common.exceptions import (
|
||||
NoSuchElementException,
|
||||
)
|
||||
from .NcContactsAutomation import NcContactsAutomation
|
||||
|
||||
class NextcloudAutomation(object):
|
||||
def __init__(self, d):
|
||||
@ -26,56 +27,52 @@ class NextcloudAutomation(object):
|
||||
d.say("Login %s to Nextcloud", login)
|
||||
d.find_el('input#user').send_text(login)
|
||||
d.find_el('input#password').send_text(pw)
|
||||
d.find_el('#submit-wrapper').click()
|
||||
submit = d.find_el('button[type="submit"]', throws=False)
|
||||
# submit button for nextcloud < 25 (jquery)
|
||||
if not submit: submit = d.find_el('#submit-wrapper') # nc<25
|
||||
submit.click()
|
||||
|
||||
def logout(self):
|
||||
d = self.d
|
||||
d.say("Logout of Nextcloud")
|
||||
d.find_el('header .avatardiv').click()
|
||||
d.find_el('#settings .avatardiv').click()
|
||||
d.find_el('[data-id="logout"] a').click()
|
||||
|
||||
def open_contacts(self):
|
||||
d = self.d
|
||||
d.say("Open contacts")
|
||||
d.find_el('header [data-id="contacts"]').click()
|
||||
# nc 25+
|
||||
el = d.find_el('header [data-app-id="contacts"]', throws=False)
|
||||
if not el:
|
||||
# nc < 25
|
||||
el = d.find_el('header [data-id="contacts"]')
|
||||
self.close_first_run_wizard()
|
||||
el.click()
|
||||
return NcContactsAutomation(self)
|
||||
|
||||
def wait_for_app_load(self, secs=7):
|
||||
d = self.d
|
||||
d.say("Wait for app to load")
|
||||
|
||||
# some apps are vue, some jquery
|
||||
|
||||
# some apps are vue, some jquery (legacy)
|
||||
vue = d.find_el('#app-content-vue', throws=False)
|
||||
if not vue: vue = d.find_el('#app-dashboard', throws=False)
|
||||
jquery = d.find_el('#app-content', throws=False)
|
||||
|
||||
if vue:
|
||||
d.say_verbose('Waiting on a vue app')
|
||||
d.execute_script('window.qa_app_loaded=false; window.setTimeout(() => { window.qa_app_loaded=true; }, 1000)');
|
||||
d.wait_until_true('return window.qa_app_loaded === true', secs=secs)
|
||||
d.wait_tick(1000)
|
||||
|
||||
elif jquery:
|
||||
d.say_verbose('Waiting on a jquery app')
|
||||
d.wait_until_true('return window.$.active == 0', secs=secs)
|
||||
|
||||
else:
|
||||
raise NoSuchElementException('#app-content or #app-content-vue')
|
||||
else:
|
||||
raise NoSuchElementException('#app-dashboard, #app-content or #app-content-vue')
|
||||
|
||||
def click_contact(self, contact):
|
||||
def close_first_run_wizard(self):
|
||||
d = self.d
|
||||
d.say("Click contact %s", contact['email'])
|
||||
found = False
|
||||
els = d.find_els('div.contacts-list div.option__details')
|
||||
d.say_verbose('found %s contacts' % len(els))
|
||||
for el in els:
|
||||
fullname = el.find_el('.option__lineone').content().strip()
|
||||
email = el.find_el('.option__linetwo').content().strip()
|
||||
d.say_verbose('contact: "%s" <%s>', fullname, email)
|
||||
if fullname.lower() == "%s %s" % (contact['givenname'].lower(), contact['surname'].lower()) and email.lower() == contact['email'].lower():
|
||||
found = True
|
||||
el.click()
|
||||
break
|
||||
if not found: raise NoSuchElementException()
|
||||
|
||||
def wait_contact_loaded(self, secs=5):
|
||||
d = self.d
|
||||
d.wait_for_el('section.contact-details', secs=secs)
|
||||
|
||||
firstrunwiz = d.find_el('#firstrunwizard', throws=False, quiet=True)
|
||||
if firstrunwiz and firstrunwiz.is_displayed():
|
||||
d.say_verbose("closing first run wizard")
|
||||
d.find_el('#firstrunwizard span.close-icon').click()
|
||||
d.wait_tick(1)
|
||||
|
@ -24,6 +24,7 @@ from selenium.common.exceptions import (
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
#
|
||||
@ -96,7 +97,10 @@ class FirefoxTestDriver(Firefox):
|
||||
|
||||
class TestDriver(object):
|
||||
def __init__(self, driver=None, verbose=None, base_url=None, output_path=None):
|
||||
self.first_start_time = None
|
||||
self.start_time = None
|
||||
self.start_msg = []
|
||||
self.next_tick_id = 0
|
||||
|
||||
if driver is None:
|
||||
if 'BROWSER_TESTS_BROWSER' in os.environ:
|
||||
@ -139,7 +143,6 @@ class TestDriver(object):
|
||||
return FirefoxTestDriver()
|
||||
raise ValueError('no such driver named "%s"' % name)
|
||||
|
||||
|
||||
def _say(self, loglevel, heirarchy_level, *args):
|
||||
if self.verbose >= loglevel:
|
||||
for i in range(len(self.start_msg), heirarchy_level+1):
|
||||
@ -163,6 +166,13 @@ class TestDriver(object):
|
||||
self._say(1, 1, *args)
|
||||
|
||||
def start(self, *args):
|
||||
now = time.time()
|
||||
if self.start_time is not None:
|
||||
elapsed = format(now - self.start_time, '.1f')
|
||||
self._say(2, 0, '[%s: %s seconds]\n', self.start_msg[0], elapsed)
|
||||
else:
|
||||
self.first_start_time = now
|
||||
self.start_time = now
|
||||
self._say(1, 0, *args)
|
||||
|
||||
def last_start(self):
|
||||
@ -264,6 +274,22 @@ class TestDriver(object):
|
||||
if throws: raise e
|
||||
else: return None
|
||||
|
||||
def wait_for_el_not_exists(self, css_selector, secs=5, throws=True):
|
||||
self.say_verbose("wait for selector '%s' (%ss) to not exist",
|
||||
css_selector, secs)
|
||||
def test_fn(driver):
|
||||
found_el = driver.find_element(By.CSS_SELECTOR, css_selector)
|
||||
if found_el: raise NoSuchElementException()
|
||||
wait = WebDriverWait(self.driver, secs, ignored_exceptions= (
|
||||
NoSuchElementException
|
||||
))
|
||||
try:
|
||||
wait.until(test_fn)
|
||||
return True
|
||||
except TimeoutException as e:
|
||||
if throws: raise e
|
||||
else: return None
|
||||
|
||||
def wait_for_text(self, text, tag='*', secs=5, exact=False, throws=True, case_sensitive=False):
|
||||
self.say_verbose("wait for text '%s'", text)
|
||||
def test_fn(driver):
|
||||
@ -278,12 +304,13 @@ class TestDriver(object):
|
||||
if throws: raise e
|
||||
else: return None
|
||||
|
||||
def find_el(self, css_selector, nth=0, throws=True):
|
||||
self.say_verbose("find element: '%s'", css_selector)
|
||||
def find_el(self, css_selector, nth=0, throws=True, quiet=False):
|
||||
try:
|
||||
els = self.driver.find_elements(By.CSS_SELECTOR, css_selector)
|
||||
if len(els)==0:
|
||||
if not quiet: self.say_verbose("find element: '%s' (not found)", css_selector)
|
||||
raise NoSuchElementException("selector=%s" % css_selector)
|
||||
if not quiet: self.say_verbose("find element: '%s' (returning #%s/%s)", css_selector, nth+1, len(els))
|
||||
return ElWrapper(self, els[nth])
|
||||
except (IndexError, NoSuchElementException) as e:
|
||||
if throws: raise e
|
||||
@ -336,12 +363,14 @@ class TestDriver(object):
|
||||
except TimeoutException as e:
|
||||
pass
|
||||
|
||||
def execute_script(self, script, *args):
|
||||
def execute_script(self, script, quiet=False, *args):
|
||||
''' Synchronously Executes JavaScript in the current window/frame '''
|
||||
newargs = []
|
||||
for arg in args:
|
||||
if isinstance(arg, ElWrapper): newargs.append(arg.el)
|
||||
else: newargs.append(arg)
|
||||
if not quiet:
|
||||
self.say_verbose('execute script: %s', script.replace('\n',' '))
|
||||
return self.driver.execute_script(script, *newargs)
|
||||
|
||||
def execute_async_script(self, script, secs=5, *args):
|
||||
@ -356,7 +385,7 @@ class TestDriver(object):
|
||||
pass
|
||||
def test_fn(driver):
|
||||
nonlocal script, args
|
||||
p = driver.execute_script(script, *args)
|
||||
p = driver.execute_script(script, quiet=True, *args)
|
||||
driver.say_verbose("script returned: %s", p)
|
||||
if not p: raise NotTrue()
|
||||
return True
|
||||
@ -364,7 +393,12 @@ class TestDriver(object):
|
||||
NotTrue
|
||||
))
|
||||
wait.until(test_fn) # throws TimeoutException
|
||||
|
||||
|
||||
def wait_tick(self, delay_ms, secs=5):
|
||||
# allow time for vue to render (delay_ms>=1)
|
||||
cancel_id = self.execute_script('window.qa_ticked=false; return window.setTimeout(() => { window.qa_ticked=true; }, %s)' % delay_ms);
|
||||
self.wait_until_true('return window.qa_ticked === true', secs=secs)
|
||||
|
||||
def close(self):
|
||||
''' close the window/tab '''
|
||||
self.say_verbose("closing %s", self.driver.current_url)
|
||||
@ -372,6 +406,10 @@ class TestDriver(object):
|
||||
|
||||
def quit(self):
|
||||
''' closes the browser and shuts down the chromedriver executable '''
|
||||
now = time.time()
|
||||
if self.first_start_time is not None:
|
||||
elapsed = format(now - self.first_start_time, '.1f')
|
||||
self._say(2, 0, '[TOTAL TIME: %s seconds]\n', elapsed)
|
||||
self.driver.quit()
|
||||
|
||||
def fail(self, exception):
|
||||
@ -395,12 +433,13 @@ class ElWrapper(object):
|
||||
self.driver = driver
|
||||
self.el = el
|
||||
|
||||
def find_el(self, css_selector, nth=0, throws=True):
|
||||
self.driver.say_verbose("find element: '%s'", css_selector)
|
||||
def find_el(self, css_selector, nth=0, throws=True, quiet=False):
|
||||
try:
|
||||
els = self.el.find_elements(By.CSS_SELECTOR, css_selector)
|
||||
if len(els)==0:
|
||||
if not quiet: self.driver.say_verbose("find element: '%s' (not found)", css_selector)
|
||||
raise NoSuchElementException("selector=%s" % css_selector)
|
||||
if not quiet: self.driver.say_verbose("find element: '%s' (returning #%s/%s)", css_selector, nth+1, len(els))
|
||||
return ElWrapper(self.driver, els[nth])
|
||||
except (IndexError, NoSuchElementException) as e:
|
||||
if throws: raise e
|
||||
|
@ -41,11 +41,16 @@ try:
|
||||
d.say_verbose('url: ' + d.current_url())
|
||||
|
||||
#
|
||||
# login, then open the contacts app
|
||||
# login
|
||||
#
|
||||
nc.login(login, pw)
|
||||
nc.wait_for_app_load()
|
||||
nc.open_contacts()
|
||||
|
||||
#
|
||||
# open Contacts
|
||||
#
|
||||
d.start("Open contacts app")
|
||||
contacts = nc.open_contacts()
|
||||
nc.wait_for_app_load()
|
||||
|
||||
#
|
||||
@ -53,18 +58,13 @@ try:
|
||||
#
|
||||
if op=='exists':
|
||||
d.start("Check that contact %s exists", contact['email'])
|
||||
nc.click_contact(contact) # raises NoSuchElementException if not found
|
||||
contacts.click_contact(contact) # raises NoSuchElementException if not found
|
||||
|
||||
elif op=='delete':
|
||||
d.start("Delete contact %s", contact['email'])
|
||||
nc.click_contact(contact)
|
||||
nc.wait_contact_loaded()
|
||||
# click "..." menu
|
||||
d.find_el('.contact-header__actions button.action-item__menutoggle').click()
|
||||
d.wait_for_el('.popover', must_be_displayed=True, secs=2)
|
||||
# click "delete"
|
||||
d.find_el('.popover span.icon-delete').parent().click()
|
||||
d.wait_for_el('div.empty-content', secs=2)
|
||||
contacts.click_contact(contact)
|
||||
contacts.wait_contact_loaded()
|
||||
contacts.delete_current_contact()
|
||||
|
||||
else:
|
||||
raise ValueError('Invalid operation: %s' % op)
|
||||
@ -72,13 +72,14 @@ try:
|
||||
#
|
||||
# logout
|
||||
#
|
||||
d.start("Logout")
|
||||
nc.logout()
|
||||
nc.wait_for_login_screen()
|
||||
|
||||
#
|
||||
# done
|
||||
#
|
||||
d.say("Success!")
|
||||
d.start("Success!")
|
||||
|
||||
except Exception as e:
|
||||
d.fail(e)
|
||||
|
@ -112,6 +112,7 @@ test_create_contact() {
|
||||
fi
|
||||
fi
|
||||
|
||||
delete_user "$alice"
|
||||
test_end
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user