selenium_tests.py 35 KB
Newer Older
Anton Petrov's avatar
Anton Petrov committed
1
"""
2
Copyright [2009-2017] EMBL-European Bioinformatics Institute
Anton Petrov's avatar
Anton Petrov committed
3 4 5 6 7 8 9 10 11 12 13
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

Anton Petrov's avatar
Anton Petrov committed
14 15 16 17 18 19 20 21
"""
	Selenium functional tests.

    The script does not rely on Django in order to avoid
    creating test databases and loading initial data.
    However, the program is included in the Django project
    to facilitate project-wide searching and renaming of html elements used for testing.

Anton Petrov's avatar
Anton Petrov committed
22 23 24 25
    The tests are designed according to a Page Object pattern,
    where each page has a class responsible for retrieving its elements.
    Each page class has an attribute "url", which should be properly
    initialized in the constructor.
Anton Petrov's avatar
Anton Petrov committed
26

27 28 29
    By default the tests use Firefox, but one can use a headless PhantomJS
    browser (http://phantomjs.org/) as an alternative.

Anton Petrov's avatar
Anton Petrov committed
30 31 32 33 34 35
    Usage:

    # test localhost
    python selenium_tests.py

    # test an RNAcentral instance
36
    python selenium_tests.py --base_url https://test.rnacentral.org/
37 38

    # test an RNAcentral instance using PhantomJS
39
    python selenium_tests.py --base_url https://test.rnacentral.org/ --driver=phantomjs
40 41 42 43

    # test a specific method in a specific test case
    python selenium_tests.py RNAcentralTest.test_url_changed_on_input_changed --driver=phantomjs

Anton Petrov's avatar
Anton Petrov committed
44 45
"""

46 47 48
from __future__ import print_function
import six

Anton Petrov's avatar
Anton Petrov committed
49
import unittest
50 51 52 53
if six.PY2:
    import urlparse
else:
    from urllib.parse import urlparse
Anton Petrov's avatar
Anton Petrov committed
54
import re
55
import sys
56
import os
57 58
import time
import urllib
59
from collections import OrderedDict
Boris A. Burkov's avatar
Boris A. Burkov committed
60

Anton Petrov's avatar
Anton Petrov committed
61
from selenium import webdriver
62
from selenium.common.exceptions import NoSuchElementException
Anton Petrov's avatar
Anton Petrov committed
63 64
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
65
from selenium.webdriver.support import expected_conditions as EC
66
from selenium.webdriver.common.keys import Keys
Anton Petrov's avatar
Anton Petrov committed
67

68 69 70
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from config import expert_databases

Anton Petrov's avatar
Anton Petrov committed
71 72

class BasePage(object):
73
    """Prototipe object for all pages."""
Anton Petrov's avatar
Anton Petrov committed
74
    base_url = None
75
    rnacentral_id_regex = r"(URS[0-9A-F]{10})"
Anton Petrov's avatar
Anton Petrov committed
76

Anton Petrov's avatar
Anton Petrov committed
77
    def __init__(self, browser, url=''):
Anton Petrov's avatar
Anton Petrov committed
78
        self.browser = browser
Anton Petrov's avatar
Anton Petrov committed
79
        self.url = self.base_url + url
Anton Petrov's avatar
Anton Petrov committed
80 81 82 83 84

    def navigate(self):
        self.browser.get(self.url)

    def get_title(self):
Anton Petrov's avatar
Anton Petrov committed
85
        return self.browser.title
Anton Petrov's avatar
Anton Petrov committed
86

87
    def js_errors_found(self):
Anton Petrov's avatar
Anton Petrov committed
88 89 90 91
        """
            All javascript errors are logged in the JSError attribute
            of the body tag.
        """
92 93 94 95 96 97
        try:
            js_errors = self.browser.find_element_by_xpath('//body[@JSError]')
            return True
        except NoSuchElementException:
            return False

Anton Petrov's avatar
Anton Petrov committed
98
    def get_svg_diagrams(self):
99
        """Check whether all svg diagrams have been generated."""
Anton Petrov's avatar
Anton Petrov committed
100 101 102
        svg = self.browser.find_elements_by_tag_name("svg")
        return len(svg)

Anton Petrov's avatar
Anton Petrov committed
103 104 105 106 107

class Homepage(BasePage):
    """RNAcentral home page"""

    def _get_example_ids(self, text):
108
        """Retrieve RNAcentral ids from some text"""
Anton Petrov's avatar
Anton Petrov committed
109 110 111 112
        rnacentral_ids = set(re.findall(self.rnacentral_id_regex, text))
        return rnacentral_ids

    def get_expert_db_example_ids(self, element_id):
113 114 115 116 117 118 119
        """
            Each expert database has a number of example entries, which are retrieved
            from the homepage by looking for an element with id in the following format:
                <expert-database>-examples
            E.g.
                mirbase-examples
        """
Anton Petrov's avatar
Anton Petrov committed
120 121 122 123 124
        example_div = self.browser.find_element_by_id(element_id)
        return self._get_example_ids(example_div.get_attribute("innerHTML"))


class SequencePage(BasePage):
125
    """Prototipe object for all types of sequence pages."""
Anton Petrov's avatar
Anton Petrov committed
126 127 128
    url = "rna/"

    def __init__(self, browser, unique_id):
Anton Petrov's avatar
Anton Petrov committed
129 130
        BasePage.__init__(self, browser, self.url)
        self.url += unique_id
Anton Petrov's avatar
Anton Petrov committed
131

132 133 134 135 136
    def navigate(self):
        super(SequencePage, self).navigate()
        WebDriverWait(self.browser, 120).until(lambda s: s.find_element_by_css_selector("#xrefs-table"))
        WebDriverWait(self.browser, 120).until(lambda s: s.find_element_by_css_selector("#d3-species-tree svg"))

Anton Petrov's avatar
Anton Petrov committed
137
    def get_xrefs_table_html(self):
138
        """Retrieve text of the database cross-reference table."""
Anton Petrov's avatar
Anton Petrov committed
139 140 141
        return self.browser.find_element_by_id("xrefs-table").text

    def get_own_rnacentral_id(self):
142
        """Get the RNAcentral id of the same page"""
Anton Petrov's avatar
Anton Petrov committed
143 144 145 146 147 148
        match = re.search(self.rnacentral_id_regex, self.get_title())
        if match:
            return match.group(1)
        else:
            raise Exception("Rnacentral id not found in the page title")

149 150 151 152 153 154 155 156 157 158 159 160 161
    def external_urls_exist(self, expert_db_name):
        """
            External urls may have class names in the following format:
                <expert_db_name>-external-url
            Example:
                mirbase-external-url
        """
        external_urls = self.browser.find_elements_by_class_name(expert_db_name + "-external-url")
        if len(external_urls) > 0:
            return True
        else:
            return False

Anton Petrov's avatar
Anton Petrov committed
162 163 164 165
    def citations_retrieved(self):
        """
            Click the first citation button on the page and then click on the abstract button.
        """
166
        timeout = 10
Anton Petrov's avatar
Anton Petrov committed
167 168 169 170 171
        self.browser.find_element_by_class_name("literature-refs-retrieve").click()
        try:
            result = WebDriverWait(self.browser, timeout).until(lambda s: s.find_element(By.CLASS_NAME, "literature-refs-content").is_displayed())
            if not result:
                raise NoSuchElementException
172
            try:
173
                citations_loaded = WebDriverWait(self.browser, timeout).until(lambda s: s.find_element_by_class_name("abstract-control").is_displayed())
174
                self.browser.find_element_by_class_name("abstract-control").click()
175
                WebDriverWait(self.browser, timeout).until(lambda s: s.find_element(By.CLASS_NAME, "abstract-text"))
176 177
            except NoSuchElementException:
                pass  # some citations don't have pubmed ids and don't have abstract buttons
Anton Petrov's avatar
Anton Petrov committed
178 179 180 181
        except:
            logging.warning('Citations not loaded ' + self.browser.current_url)
            return False
        return True
Anton Petrov's avatar
Anton Petrov committed
182 183


Anton Petrov's avatar
Anton Petrov committed
184 185
class TaxidFilteringSequencePage(SequencePage):
    """
186
    Class for testing sequence page customization based on taxid.
Anton Petrov's avatar
Anton Petrov committed
187 188
    """

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
    def get_info_text(self):
        """
        """
        info_box = self.browser.find_element_by_class_name('well')
        return info_box.text

    def get_page_subtitle(self):
        """
        Page subtitle is either the name of the species or the number of species.
        """
        subtitle = self.browser.find_element_by_css_selector('h1 small')
        return subtitle.text

    def get_warning_info_text(self):
        """
        When a taxid from the url doesn't match any annotations, a warning should be displayed.
        """
        warning = self.browser.find_element_by_class_name('alert-danger')
        return warning.text

    def get_species_from_xref_table(self):
        """
        When taxid filtering is enabled, the xref table should have annotations
        only from one species.
        """
        tds = self.browser.find_elements_by_css_selector('#xrefs-table td')
        species = [x.text for x in tds[2::3]] #3, 6, 9 etc tds
        return set(species)

Anton Petrov's avatar
Anton Petrov committed
218

219 220
class GencodeSequencePage(SequencePage):
    """Sequence page with GENCODE xrefs."""
Anton Petrov's avatar
Anton Petrov committed
221 222

    def gene_and_transcript_is_ok(self):
223
        """
224 225
            Find transcipt info, e.g.:
            transcript ENST00000626826.1 (Havana id OTTHUMT00000479989)
Anton Petrov's avatar
Anton Petrov committed
226 227
        """
        xrefs_table = self.get_xrefs_table_html()
228
        match = re.search(r'transcript ENST\d+', xrefs_table)
Anton Petrov's avatar
Anton Petrov committed
229 230 231
        return True if match else False

    def alternative_transcripts_is_ok(self):
232
        # to do
Anton Petrov's avatar
Anton Petrov committed
233 234 235 236
        return True


class TmRNASequencePage(SequencePage):
237
    """Sequence page with tmRNA Website xrefs."""
Anton Petrov's avatar
Anton Petrov committed
238 239

    def test_one_piece_tmrna(self):
240
        # to do
Anton Petrov's avatar
Anton Petrov committed
241 242 243
        return True

    def test_two_piece_tmrna(self):
244
        """Make sure partner tmRNAs link to each other."""
Anton Petrov's avatar
Anton Petrov committed
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
        xrefs_table = self.get_xrefs_table_html()
        match = re.search(r'Two-piece tmRNA partner: ' + self.rnacentral_id_regex, xrefs_table)
        if match:
            original_id = self.get_own_rnacentral_id()
            partner_id = match.group(1)
            partner_page = TmRNASequencePage(self.browser, partner_id)
            partner_page.navigate()
            if original_id in self.browser.page_source:
                return True
            else:
                return False
        else:
            return True

    def test_precursor_tmrna(self):
260
        # to do
Anton Petrov's avatar
Anton Petrov committed
261 262 263 264
        return True


class MirbaseSequencePage(SequencePage):
Anton Petrov's avatar
Anton Petrov committed
265
    """Sequence page with miRBase xrefs. Add miRBase-specific tests here."""
266
    pass
Anton Petrov's avatar
Anton Petrov committed
267 268


Anton Petrov's avatar
Anton Petrov committed
269 270 271 272 273 274 275 276 277 278 279 280
class SrpdbSequencePage(SequencePage):
    """Sequence page with SRPdb xrefs. Add SRPdb-specific tests here."""
    pass


class RefseqSequencePage(SequencePage):
    """Sequence page with RefSeq xrefs. Add RefSeq-specific tests here."""
    pass


class RdpSequencePage(SequencePage):
    """Sequence page with Rdp xrefs. Add RefSeq-specific tests here."""
281
    pass
Anton Petrov's avatar
Anton Petrov committed
282 283


284
class ExpertDatabasesOverviewPage(BasePage):
285
    """An overview page for all expert databases."""
286 287 288
    url = 'expert-databases/'

    def __init__(self, browser):
Anton Petrov's avatar
Anton Petrov committed
289
        BasePage.__init__(self, browser, self.url)
290

291
    def get_expert_tr_count(self):
292
        """get the number of rows representing expert databases."""
293
        expert_dbs = self.browser.find_elements_by_tag_name("tr")
294 295
        return len(expert_dbs)

296
    def get_footer_expert_db_count(self):
297
        """get the number of expert database links in the footer."""
Anton Petrov's avatar
Anton Petrov committed
298
        expert_dbs = self.browser.find_elements_by_css_selector('#global-footer .col-md-8 li>a')
299 300
        return len(expert_dbs)

301

302
class ExpertDatabaseLandingPage(BasePage):
303
    """An expert database landing page."""
304 305 306
    url = 'expert-database/'

    def __init__(self, browser, expert_db_id):
Anton Petrov's avatar
Anton Petrov committed
307 308
        BasePage.__init__(self, browser, self.url)
        self.url += expert_db_id
309
        self.expert_db_id = expert_db_id
310

311 312 313 314 315
    def get_svg_diagrams(self):
        """
            Make sure all svg are generated.
            Override the default behavior because of the ajax requests.
        """
316 317
        NO_SUNBURST = ['ena', 'rfam']
        NO_SEQ_DIST = ['lncrnadb']
318
        try:
319 320 321 322
            if self.expert_db_id not in NO_SUNBURST:
                sunburst = self.browser.find_element(By.CSS_SELECTOR, "#d3-species-sunburst svg")
            if self.expert_db_id not in NO_SEQ_DIST:
                seq_dist = self.browser.find_element(By.CSS_SELECTOR, "#d3-seq-length-distribution svg")
323 324 325 326
        except:
            return False
        return True

Anton Petrov's avatar
Anton Petrov committed
327 328

class GenoverseTestPage(BasePage):
329
    """A sequence page with an embedded genome browser."""
330
    url = 'rna/'
Anton Petrov's avatar
Anton Petrov committed
331

332
    def __init__(self, browser, rnacentral_id):
Anton Petrov's avatar
Anton Petrov committed
333
        BasePage.__init__(self, browser, self.url)
334
        self.url += rnacentral_id
Anton Petrov's avatar
Anton Petrov committed
335 336

    def genoverse_ok(self):
337 338
        genoverse_button = WebDriverWait(self.browser, 30).until(lambda s: s.find_element(By.CLASS_NAME, "genoverse-xref"))
        genoverse_button.click()
Anton Petrov's avatar
Anton Petrov committed
339 340 341 342 343
        try:
            WebDriverWait(self.browser, 5).until(lambda s: s.find_element(By.CSS_SELECTOR, "table.genoverse").is_displayed())
        except:
            return False
        return True
344 345


346 347
class GenomeBrowserPage(BasePage):
    """A standalone Genoverse genome browser page."""
348
    url = 'genome-browser/'
349
    timeout = 10
350 351 352 353

    def __init__(self, browser):
        BasePage.__init__(self, browser, self.url)

354 355
    @property
    def start_input(self):
356 357 358
        return WebDriverWait(self.browser, self.timeout).until(
            EC.presence_of_element_located((By.ID, "genomic-start-input"))
        )
359 360 361

    @property
    def end_input(self):
362 363 364
        return WebDriverWait(self.browser, self.timeout).until(
            EC.presence_of_element_located((By.ID, "genomic-end-input"))
        )
365 366 367

    @property
    def chromosome_input(self):
368 369 370
        return WebDriverWait(self.browser, self.timeout).until(
            EC.presence_of_element_located((By.ID, "chromosome-input"))
        )
371 372 373

    @property
    def species_input(self):
374 375 376
        return WebDriverWait(self.browser, self.timeout).until(
            EC.presence_of_element_located((By.ID, "genomic-species-select"))
        )
377 378 379

    @property
    def ensembl_link(self):
380 381 382
        return WebDriverWait(self.browser, self.timeout).until(
            EC.presence_of_element_located((By.ID, "ensembl-link"))
        )
383 384 385

    @property
    def ucsc_link(self):
386 387 388
        return WebDriverWait(self.browser, self.timeout).until(
            EC.presence_of_element_located((By.ID, "ucsc-link"))
        )
389

390

391
class TextSearchPage(BasePage):
392
    """Can be any page because the search box is in the site-wide header."""
393
    url = ''
394
    timeout = 10  # seconds to wait for element to appear
395

396
    def __init__(self, browser, query_url=''):
397
        BasePage.__init__(self, browser, self.url)
398
        self.url += query_url
399
        self.page_size = 15
400

401
    # DOM elements as properties
Boris A. Burkov's avatar
Boris A. Burkov committed
402
    # --------------------------
403 404

    @property
Boris A. Burkov's avatar
Boris A. Burkov committed
405
    def input(self):
406
        return WebDriverWait(self.browser, self.timeout).until(
Boris A. Burkov's avatar
Boris A. Burkov committed
407
            EC.presence_of_element_located((By.CSS_SELECTOR, '.global-search input'))
408 409 410
        )

    @property
Boris A. Burkov's avatar
Boris A. Burkov committed
411
    def submit_button(self):
412
        return WebDriverWait(self.browser, self.timeout).until(
Boris A. Burkov's avatar
Boris A. Burkov committed
413
            EC.presence_of_element_located((By.CSS_SELECTOR, '.global-search button'))
414 415
        )

416 417 418 419 420 421
    @property
    def autocomplete_suggestions(self):
        return WebDriverWait(self.browser, self.timeout).until(
            EC.visibility_of_any_elements_located((By.CSS_SELECTOR, ".global-search li.uib-typeahead-match"))
        )

422 423 424 425 426 427
    @property
    def examples(self):
        return WebDriverWait(self.browser, self.timeout).until(
            EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.example-searches a'))
        )

428 429 430 431 432
    @property
    def unchecked_facet_link(self):
        return WebDriverWait(self.browser, self.timeout).until(
            # pick a link next to an unchecked checkbox
            EC.element_to_be_clickable(
433
                (By.CSS_SELECTOR, ".text-search-facet-values input[type=checkbox]:not(:checked) ~ a")
434 435 436
            )
        )

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
    @property
    def facet_links(self):
        return WebDriverWait(self.browser, self.timeout).until(
            EC.visibility_of_any_elements_located((By.CSS_SELECTOR, 'a.text-search-facet-link'))
        )

    @property
    def rna_types_facet(self):
        return WebDriverWait(self.browser, self.timeout).until(
            EC.visibility_of_any_elements_located((By.CSS_SELECTOR, '.text-search-facet-values'))
        )[0]

    @property
    def organisms_facet(self):
        return WebDriverWait(self.browser, self.timeout).until(
            EC.visibility_of_any_elements_located((By.CSS_SELECTOR, '.text-search-facet-values'))
        )[1]

    @property
    def expert_databases_facet(self):
        return WebDriverWait(self.browser, self.timeout).until(
            EC.visibility_of_any_elements_located((By.CSS_SELECTOR, '.text-search-facet-values'))
        )[2]

    @property
    def genomic_mapping_facet(self):
        return WebDriverWait(self.browser, self.timeout).until(
            EC.visibility_of_any_elements_located((By.CSS_SELECTOR, '.text-search-facet-values'))
        )[3]

467 468
    @property
    def load_more_button(self):
Boris A. Burkov's avatar
Boris A. Burkov committed
469
        return WebDriverWait(self.browser, self.timeout).until(
470 471 472 473
            EC.element_to_be_clickable((By.CLASS_NAME, 'load-more'))
        )

    @property
474
    def text_search_results_count(self):
475
        return WebDriverWait(self.browser, self.timeout).until(
476
            EC.visibility_of_element_located((By.ID, "text-search-results-count"))
477 478 479
        )

    @property
480
    def text_search_results(self):
481
        """Get results as an array of list elements."""
Boris A. Burkov's avatar
Boris A. Burkov committed
482 483
        return WebDriverWait(self.browser, self.timeout).until(
            EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".result"))  # was: lambda browser: browser.find_elements(By.CLASS_NAME, "result")
484
        )
485

Boris A. Burkov's avatar
Boris A. Burkov committed
486 487 488
    @property
    def warnings(self):
        return WebDriverWait(self.browser, self.timeout).until(
489
            EC.visibility_of_element_located((By.CLASS_NAME, "text-search-no-results"))  # was: lambda s: s.find_element(By.CLASS_NAME, "text-search-no-results"
Boris A. Burkov's avatar
Boris A. Burkov committed
490 491 492 493 494
        )

    # functions
    # ---------

495
    def test_example_searches(self):
496
        """Click on example searches in the header and make sure there are results."""
497 498

        def click_load_more():
499
            """Click the Load more button and verify the number of results."""
500
            for i in [2, 3, 4]:
501 502 503 504 505 506 507 508
                try:  # load_more_button might be available or might not
                    button = self.load_more_button
                except:  # there's no load_more_button - ok, just return
                    return
                else:  # load_more_button exists - click it and expect more results
                    button.click()
                    WebDriverWait(self.browser, self.timeout).until(
                        EC.text_to_be_present_in_element(
509
                            (By.ID, "text-search-results-count"),
510 511
                            '%i out of ' % (i * self.page_size)
                        )
512 513
                    )

514 515 516 517 518 519
        def enable_facet():
            """
            Select a facet at random and enable it. Make sure that the results
            are filtered correctly.
            """
            # get the number of entries in the facet
520 521 522
            facet_count = re.search(r'\((.+?)\)$', self.unchecked_facet_link.text)
            self.unchecked_facet_link.click()
            WebDriverWait(self.browser, self.timeout).until(
523
                EC.text_to_be_present_in_element(
524
                    (By.ID, "text-search-results-count"),
525 526 527 528
                    'out of %s' % facet_count.group(1)
                )
            )

529
        success = []
530
        self.browser.maximize_window()  # sometimes phantomjs cannot find elements without this
531
        for example in self.examples:
Anton Petrov's avatar
Anton Petrov committed
532
            results = []
533
            example.click()
534
            results = self.text_search_results
535 536 537
            if len(results) > self.page_size:
                click_load_more()
            enable_facet()
538 539
            if len(results) > 0:
                success.append(1)
540
        return len(success) == len(self.examples)
541

Boris A. Burkov's avatar
Boris A. Burkov committed
542 543 544
    def _submit_search_by_return_key(self, query):
        self.input.send_keys(query)
        self.input.send_keys(Keys.RETURN)
545

Boris A. Burkov's avatar
Boris A. Burkov committed
546 547 548
    def _submit_search_by_submit_button(self, query):
        self.input.send_keys(query)
        self.submit_button.click()
549

550
    def warnings_present(self):
551
        """
552
        div.text-search-no-results is only generated when a search returns
553 554
        zero results. This test makes sure that the element is present and
        displayed.
555
        """
556
        try:
557
            WebDriverWait(self.browser, self.timeout).until(
558
                EC.visibility_of_element_located((By.CLASS_NAME, "text-search-no-results"))
559
            )
Boris A. Burkov's avatar
Boris A. Burkov committed
560
            return True  # was: warning.is_displayed()
561 562
        except:
            return False
563

564

Anton Petrov's avatar
Anton Petrov committed
565
class RNAcentralTest(unittest.TestCase):
566
    """Unit tests entry point."""
567
    driver = 'firefox'
Anton Petrov's avatar
Anton Petrov committed
568 569

    def setUp(self):
570 571 572 573 574 575
        if self.driver == 'firefox':
            self.browser = webdriver.Firefox()
        elif self.driver == 'phantomjs':
            self.browser = webdriver.PhantomJS()
        else:
            sys.exit('Driver not found')
Anton Petrov's avatar
Anton Petrov committed
576 577

    def tearDown(self):
578
        self.browser.quit()
Anton Petrov's avatar
Anton Petrov committed
579

580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
    # Helper functions
    # ----------------

    def _get_expert_db_example_ids(self, expert_db_id):
        """Retrieve example RNAcentral ids from the homepage."""
        homepage = Homepage(self.browser)
        homepage.navigate()
        return homepage.get_expert_db_example_ids(expert_db_id)

    def _sequence_view_checks(self, page):
        self.assertTrue(page.citations_retrieved())
        self.assertFalse(page.js_errors_found())
        self.assertEqual(page.get_svg_diagrams(), 1)

    # Basic tests
    # -----------

Anton Petrov's avatar
Anton Petrov committed
597
    def test_homepage(self):
Anton Petrov's avatar
Anton Petrov committed
598 599 600
        page = Homepage(self.browser)
        page.navigate()
        self.assertFalse(page.js_errors_found())
Anton Petrov's avatar
Anton Petrov committed
601
        self.assertIn("RNAcentral", page.get_title())
Anton Petrov's avatar
Anton Petrov committed
602

603
    def test_browser_back_button(self):
604
        """Make sure browser history is recorded correctly."""
605 606
        history = ['contact', 'downloads', 'search?q=mirbase',
                   'search?q=foobar']
607
        page = TextSearchPage(self.browser)
608 609 610 611 612 613 614
        for item in history:
            page.browser.get(page.base_url + item)
            time.sleep(2)
        for item in reversed(history):
            self.assertIn(urllib.quote(item), urllib.quote(page.browser.current_url))
            page.browser.back()

615 616
    # Text search
    # -----------
617

618
    def test_text_search_examples(self):
619
        """Test text search examples, can be done on any page."""
620
        page = TextSearchPage(self.browser)
621 622 623
        page.navigate()
        self.assertTrue(page.test_example_searches())

624
    def test_text_search_no_results(self):
625
        """
626
        Run a text search query that won't find any results, make sure that
627 628
        no results are displayed.
        """
629
        page = TextSearchPage(self.browser)
630
        page.navigate()
Anton Petrov's avatar
Anton Petrov committed
631
        query = 'foobarbaz'
Boris A. Burkov's avatar
Boris A. Burkov committed
632
        page._submit_search_by_submit_button(query)
633
        self.assertTrue(page.warnings_present())
634

635
    def test_text_search_no_warnings(self):
636
        """
637
        Run a text search query that will find results, make sure that some
Anton Petrov's avatar
Anton Petrov committed
638
        results are displayed. The opposite of `test_text_search_no_results`.
639
        """
640
        page = TextSearchPage(self.browser)
641 642
        page.navigate()
        query = 'RNA'
Boris A. Burkov's avatar
Boris A. Burkov committed
643
        page._submit_search_by_submit_button(query)
644
        self.assertFalse(page.warnings_present())
645

646
    def test_text_search_grouping_operators(self):
647
        """Test a query with logical operators and query grouping."""
648
        page = TextSearchPage(self.browser)
649 650
        page.navigate()
        query = '(expert_db:"mirbase" OR expert_db:"lncrnadb") NOT expert_db:"rfam"'
Boris A. Burkov's avatar
Boris A. Burkov committed
651
        page._submit_search_by_submit_button(query)
652
        self.assertTrue(len(page.text_search_results) > 0)
653

654
    def test_text_search_load_search_url(self):
655
        """Load a text search using a search url."""
656
        page = TextSearchPage(self.browser, 'search?q=mirbase')
657
        page.navigate()
658
        self.assertTrue(len(page.text_search_results) > 0)
659

660
    def test_text_search_species_specific_filtering(self):
661
        """Make sure that URS/taxid and URS_taxid are found in text search."""
662
        # forward slash
663
        page = TextSearchPage(self.browser, 'search?q=URS000047C79B/9606')
664
        page.navigate()
665
        self.assertEqual(len(page.text_search_results), 1)
666
        # underscore
667
        page = TextSearchPage(self.browser, 'search?q=URS000047C79B_9606')
668
        page.navigate()
669
        self.assertEqual(len(page.text_search_results), 1)
670
        # non-existing taxid
671
        page = TextSearchPage(self.browser, 'search?q=URS000047C79B_00000')
672 673 674
        page.navigate()
        self.assertTrue(page.warnings_present())

675
    def test_autocomplete_test_suite(self):
676
        """A collection of queries to check correctness of autocomplete suggestions."""
677 678 679 680 681
        test_suite = [
            'mir 12',
            'lncrna',
            'mitochondrial',  # sic! - typo is intentional
            'kcnq1ot1',
682 683

            # key species
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
            'Arabidopsis thaliana',
            'Bombyx mori',
            'Bos taurus',
            'Caenorhabditis elegans',
            'Canis familiaris',
            'Danio rerio',
            'Drosophila melanogaster',
            'Homo sapiens',
            'Mus musculus',
            'Pan troglodytes',
            'Rattus norvegicus',
            'Schizosaccharomyces pombe',
            'arabidopsis',
            'mosquito',
            'bombyx',
            'caenorhabditis',
            'nematode',
            'fish',
            'drosophila',
            'human',
            'homo',
            'mouse',
            'chimpanzee',
            'chimp',
            'rattus',
        ]
710 711

        # add expert databases names to test_suites - their names should be suggested by autocomplete
Anton Petrov's avatar
Anton Petrov committed
712
        expert_dbs = [db['name'] for db in expert_databases.expert_dbs if db['imported']]
713
        test_suite += expert_dbs
714

715
        page = TextSearchPage(self.browser)
716 717
        page.navigate()

718 719 720 721 722 723
        for query in test_suite:
            page.input.clear()
            page.input.send_keys(query)
            try:
                page.autocomplete_suggestions
            except:
724
                print("Failed: query %s has no suggestions" % query)
725 726 727
                continue
            suggestions = [suggestion.text.lower() for suggestion in page.autocomplete_suggestions]
            if not query.lower() in suggestions:
728
                print("Failed: query = %s not found in suggestions = %s" % (query, suggestions))
729

730 731 732 733 734 735 736 737
    def test_text_search_test_suite(self):
        """
        A collection of queries, obtained as a feedback from SAB
        and our own assumptions about what queries could be useful.
        """
        # the dict has the following structure
        # {query: [hits that are expected to appear in results list]}
        test_suite = OrderedDict([
738 739 740 741
            ('bantam AND Taxonomy:"7227"', ['URS000055786A_7227', 'URS00004E9E38_7227', 'URS00002F21DA_7227']),
            ('U12', ['URS000075EF5D_9606']),
            ('ryhB', ['URS00003CF5BC_511145']),
            ('coolair', ['URS000018EB2E_3702']),
742 743 744 745 746 747 748 749 750 751 752 753 754
            ('tRNA-Phe', ['URS00003A0C47_9606']),
            ('("HOTAIR" OR "HOX") AND TAXONOMY:"9606" AND rna_type:"lncRNA" AND length:[500 to 3000]', [
                'URS000075C808_9606',  # HGNC HOTAIR Gene
                'URS0000301B08_9606',  # GENCODE/Ensembl Gene
                'URS0000759B00_9606',  # RefSeq transcript variant
                'URS000075EF05_9606',  # RefSeq transcript variant
                'URS00001A335C_9606',  # GENCODE/Ensembl transcript
            ]),
            ('4V4Q', [
                'URS00004B0F34_562',  # LSU
                'URS00000ABFE9_562',  # SSU
                'URS0000049E57_562',  # 5S
            ]),
755 756 757 758 759 760 761 762 763
        ])

        page = TextSearchPage(self.browser)
        page.navigate()

        for query, expected_results in test_suite.items():
            page.input.clear()
            page._submit_search_by_submit_button(query)

764 765
            assert page.text_search_results_count
            for expected_result in expected_results:
Boris A. Burkov's avatar
Boris A. Burkov committed
766
                is_found = False
767 768
                for result in page.text_search_results:
                    if expected_result in result.text:
Boris A. Burkov's avatar
Boris A. Burkov committed
769
                        is_found = True
770
                        break  # ok, result found, move on to the next expected_result
Boris A. Burkov's avatar
Boris A. Burkov committed
771
                if not is_found:  # if we managed to get here, expected_result is not found in results - fail
772
                    print("Expected result %s not found for query %s" % (expected_result, query))  # or raise AssertionError
773

Anton Petrov's avatar
Anton Petrov committed
774 775 776 777 778 779
    def test_text_search_facets(self):
        """
        Check that facet values make sense.
        """
        page = TextSearchPage(self.browser)
        page.navigate()
780 781 782 783 784 785 786 787 788 789 790 791 792

        # mirbase
        page.input.clear()
        page._submit_search_by_submit_button("mirbase")
        for element in page.rna_types_facet.find_elements_by_css_selector('li > a'):
            assert re.match('miRNA', element.text) or re.match('precursor RNA', element.text)

        # hgnc
        page.input.clear()
        page._submit_search_by_submit_button("hgnc")
        for element in page.organisms_facet.find_elements_by_css_selector('li > a'):
            assert re.match('Homo sapiens', element.text)

Anton Petrov's avatar
Anton Petrov committed
793 794 795 796 797 798
        # dictyBase
        page.input.clear()
        page._submit_search_by_submit_button('expert_db:"dictyBase"')
        for element in page.organisms_facet.find_elements_by_css_selector('li > a'):
            assert re.match('Dictyostelium discoideum AX4', element.text)

799

800 801 802
    # Sequence pages for specific databases
    # -------------------------------------

Anton Petrov's avatar
Anton Petrov committed
803
    def test_all_expert_database_page(self):
Anton Petrov's avatar
Anton Petrov committed
804 805 806
        page = ExpertDatabasesOverviewPage(self.browser)
        page.navigate()
        self.assertFalse(page.js_errors_found())
807
        self.assertEqual(page.get_expert_tr_count(), page.get_footer_expert_db_count() + 1)
Anton Petrov's avatar
Anton Petrov committed
808 809

    def test_tmrna_website_example_pages(self):
810
        for example_id in self._get_expert_db_example_ids('tmrna-website-examples'):
Anton Petrov's avatar
Anton Petrov committed
811 812 813 814 815 816
            page = TmRNASequencePage(self.browser, example_id)
            page.navigate()
            self._sequence_view_checks(page)
            self.assertTrue(page.test_one_piece_tmrna())
            self.assertTrue(page.test_two_piece_tmrna())
            self.assertTrue(page.test_precursor_tmrna())
Anton Petrov's avatar
Anton Petrov committed
817

818 819 820
    def test_gencode_example_pages(self):
        for example_id in self._get_expert_db_example_ids('gencode-examples'):
            page = GencodeSequencePage(self.browser, example_id)
Anton Petrov's avatar
Anton Petrov committed
821 822 823 824
            page.navigate()
            self._sequence_view_checks(page)
            self.assertTrue(page.gene_and_transcript_is_ok())
            self.assertTrue(page.alternative_transcripts_is_ok())
Anton Petrov's avatar
Anton Petrov committed
825

826 827 828 829 830 831
    def test_refseq_example_pages(self):
        for example_id in self._get_expert_db_example_ids('refseq-examples'):
            page = RefseqSequencePage(self.browser, example_id)
            page.navigate()
            self._sequence_view_checks(page)

Anton Petrov's avatar
Anton Petrov committed
832 833 834 835 836 837
    def test_rdp_example_pages(self):
        for example_id in self._get_expert_db_example_ids('rdp-examples'):
            page = RdpSequencePage(self.browser, example_id)
            page.navigate()
            self._sequence_view_checks(page)

838 839
    def test_mirbase_example_pages(self):
        for example_id in self._get_expert_db_example_ids('mirbase-examples'):
Anton Petrov's avatar
Anton Petrov committed
840 841 842 843
            page = MirbaseSequencePage(self.browser, example_id)
            page.navigate()
            self._sequence_view_checks(page)
            self.assertTrue(page.external_urls_exist('mirbase'))
Anton Petrov's avatar
Anton Petrov committed
844

845 846
    def test_srpdb_example_pages(self):
        for example_id in self._get_expert_db_example_ids('srpdb-examples'):
Anton Petrov's avatar
Anton Petrov committed
847
            page = SrpdbSequencePage(self.browser, example_id)
Anton Petrov's avatar
Anton Petrov committed
848 849 850
            page.navigate()
            self._sequence_view_checks(page)
            self.assertTrue(page.external_urls_exist('srpdb'))
851 852

    def test_expert_database_landing_pages(self):
853
        expert_dbs = ['tmrna-website', 'srpdb', 'mirbase', 'gencode',
Anton Petrov's avatar
Anton Petrov committed
854
                      'ena', 'rfam', 'lncrnadb', 'gtrnadb', 'refseq', 'rdp']
855
        for expert_db in expert_dbs:
Anton Petrov's avatar
Anton Petrov committed
856 857 858
            page = ExpertDatabaseLandingPage(self.browser, expert_db)
            page.navigate()
            self.assertFalse(page.js_errors_found())
859
            self.assertTrue(page.get_svg_diagrams())
860

Anton Petrov's avatar
Anton Petrov committed
861
    def test_xref_pagination(self):
862 863
        """Test xref table pagination."""

Anton Petrov's avatar
Anton Petrov committed
864
        class XrefPaginationPage(SequencePage):
865
            """Get the active xref page number."""
Anton Petrov's avatar
Anton Petrov committed
866 867 868 869 870 871 872 873 874 875
            def get_active_xref_page_num(self):
                active_button = self.browser.find_element_by_css_selector('li.active>a.xref-pagination')
                return active_button.text

        upi = 'URS00006EC23D'
        xref_page_num = '5'
        page = XrefPaginationPage(self.browser, upi + '?xref-page=' + xref_page_num)
        page.navigate()
        self.assertTrue(page.get_active_xref_page_num(), xref_page_num)
        self._sequence_view_checks(page)
876

877