def print_upstream_linking_form(browser): """Print the upstream linking form found via +choose-affected-product. The resulting output will look something like: (*) A checked option [A related text field] ( ) An unchecked option """ soup = BeautifulSoup(browser.contents) link_upstream_how_radio_control = browser.getControl( name='field.link_upstream_how') link_upstream_how_buttons = soup.findAll( 'input', {'name': 'field.link_upstream_how'}) wrapper = textwrap.TextWrapper(width=65, subsequent_indent=' ') for button in link_upstream_how_buttons: # Print the radio button. label = button.findParent('label') if label is None: label = soup.find('label', {'for': button['id']}) if button.get('value') in link_upstream_how_radio_control.value: print wrapper.fill('(*) %s' % extract_text(label)) else: print wrapper.fill('( ) %s' % extract_text(label)) # Print related text field, if found. Assumes that the text # field is in the same table row as the radio button. text_field = button.findParent('tr').find('input', {'type': 'text'}) if text_field is not None: text_control = browser.getControl(name=text_field.get('name')) print ' [%s]' % text_control.value.ljust(10)
def test_does_display_child_diff(self): # If the child's latest published version is not the same as the base # version, we display two links to two diffs. changelog_lfa = self.factory.makeChangelog('foo', ['0.1-1derived1', '0.1-1']) parent_changelog_lfa = self.factory.makeChangelog( 'foo', ['0.1-2', '0.1-1']) transaction.commit() # Yay, librarian. ds_diff = self.factory.makeDistroSeriesDifference( versions={ 'derived': '0.1-1derived1', 'parent': '0.1-2', }, changelogs={ 'derived': changelog_lfa, 'parent': parent_changelog_lfa }) self.assertEqual('0.1-1', ds_diff.base_version) with person_logged_in(self.factory.makePerson()): view = create_initialized_view(ds_diff, '+listing-distroseries-extra') soup = BeautifulSoup(view()) tags = soup.find('ul', 'package-diff-status').findAll('span') self.assertEqual(2, len(tags))
def test_rendering(self): some_repo = self.factory.makeGitRepository( owner=self.owner, target=self.target, name="foo") self.factory.makeGitRefs( some_repo, paths=["refs/heads/master", "refs/heads/bug-1234"]) other_repo = self.factory.makeGitRepository( owner=self.owner, target=self.target, name="bar") self.factory.makeGitRefs(other_repo, paths=["refs/heads/bug-2468"]) view = create_initialized_view(self.context, '+git') self.assertIs(None, view.default_git_repository) content = view() soup = BeautifulSoup(content) # No details about the default repo are shown, as a person # without a target doesn't have a default repo self.assertNotIn('Branches', content) self.assertNotIn('Browse the code', content) self.assertNotIn('git clone', content) self.assertNotIn('bug-1234', content) # All owned repos are listed. table = soup.find( 'div', id='gitrepositories-table-listing').find('table') self.assertContentEqual( [some_repo.git_identity, other_repo.git_identity], [link.find(text=True) for link in table.findAll('a')])
def test_sharing_menu(self): url = canonical_url(self.pillar) sharing_url = canonical_url(self.pillar, view_name='+sharing') browser = setupBrowserForUser(user=self.driver) browser.open(url) soup = BeautifulSoup(browser.contents) sharing_menu = soup.find('a', {'href': sharing_url}) self.assertIsNotNone(sharing_menu)
def contains_one_link_to_diff(self, html_or_soup, package_diff): """Return whether the html contains a link to the diff content.""" if not (isinstance(html_or_soup, BeautifulSoup)): soup = BeautifulSoup(html_or_soup) else: soup = html_or_soup return 1 == len( soup.findAll('a', href=package_diff.diff_content.http_url))
def number_of_request_diff_texts(self, html_or_soup): """Returns the number of request diff text.""" if not (isinstance(html_or_soup, BeautifulSoup)): soup = BeautifulSoup(html_or_soup) else: soup = html_or_soup class_dict = {'class': re.compile('request-derived-diff')} return len(soup.findAll('span', class_dict))
def test_product_with_edit_permission(self): login_person(self.product.owner) self.view = create_initialized_view( self.product, '+questions', principal=self.product.owner) content = BeautifulSoup(self.view()) self.assertCommonPageElements(content) self.assertTrue( content.find(True, id='configure-support') is not None)
def test_call(self): # The __call__ method sets up the widgets and the options. markup = self.widget() self.assertIsNotNone(self.widget.person_widget) self.assertIn("repository_owner", self.widget.options) self.assertIn("person", self.widget.options) soup = BeautifulSoup(markup) fields = soup.findAll(["input", "select"], {"id": re.compile(".*")}) ids = [field["id"] for field in fields] self.assertContentEqual(self.expected_ids, ids)
def test_call(self): # The __call__ method sets up the widgets. markup = self.widget() self.assertIsNotNone(self.widget.repository_widget) self.assertIsNotNone(self.widget.path_widget) soup = BeautifulSoup(markup) fields = soup.findAll("input", id=True) ids = [field["id"] for field in fields] self.assertContentEqual( ["field.git_ref.repository", "field.git_ref.path"], ids)
def get_text_values(self, bug): """Returns the text of status and substatus for the given bug. This is done by downloading the HTML bug page and scraping it. """ url = '%s%s' % (self.base_url, bug['id']) page = self.fetch(url).read() soup = BeautifulSoup(page) return tuple( node.string for node in soup.find('th', text='Status').findNext('td').findAll('span'))
def test_filebug_information_type_public_policy(self): # The vocabulary for information_type when filing a bug is created # correctly for non commercial projects. product = self.factory.makeProduct(official_malone=True) with person_logged_in(product.owner): view = create_initialized_view(product, '+filebug', principal=product.owner) html = view.render() soup = BeautifulSoup(html) self.assertIsNone(soup.find('label', text="Proprietary"))
def test_view_contents(self): # Distributions are rendered with the correct text. url = canonical_url(self.pillar, view_name='+sharing') browser = setupBrowserForUser(user=self.driver) browser.open(url) soup = BeautifulSoup(browser.contents) commercial_text = soup.find('p', {'id': 'commercial-project-text'}) non_commercial_text = soup.find( 'p', {'id': 'non-commercial-project-text'}) self.assertIsNone(commercial_text) self.assertIsNone(non_commercial_text)
def test_blacklist_options_disabled(self): # Blacklist options are disabled to the users who are *not* archive # admins. ds_diff = self.factory.makeDistroSeriesDifference() person = self.factory.makePerson() view_content = self.getViewContentXmlHttpRequest( ds_diff, '+listing-distroseries-extra', person) soup = BeautifulSoup(view_content) self.assertEqual( 1, len(soup.findAll('div', {'class': 'blacklist-options-disabled'})))
def test_filebug_information_type_vocabulary(self): # The vocabulary for information_type when filing a bug is created # correctly. product = self.factory.makeProduct(official_malone=True) with person_logged_in(product.owner): view = create_initialized_view(product, '+filebug', principal=product.owner) html = view.render() soup = BeautifulSoup(html) for info_type in product.getAllowedBugInformationTypes(): self.assertIsNotNone(soup.find('label', text=info_type.title))
def test_view_contents_commercial_project(self): # Commercial projects are rendered with the correct text. self.factory.makeCommercialSubscription(self.pillar) url = canonical_url(self.pillar, view_name='+sharing') browser = setupBrowserForUser(user=self.driver) browser.open(url) soup = BeautifulSoup(browser.contents) commercial_text = soup.find('p', {'id': 'commercial-project-text'}) non_commercial_text = soup.find( 'p', {'id': 'non-commercial-project-text'}) self.assertIsNotNone(commercial_text) self.assertIsNone(non_commercial_text)
def test_blacklist_options(self): # Blacklist options are presented to the users who are archive # admins. ds_diff = self.factory.makeDistroSeriesDifference() archive_admin = self.factory.makeArchiveAdmin( archive=ds_diff.derived_series.main_archive) view_content = self.getViewContentXmlHttpRequest( ds_diff, '+listing-distroseries-extra', archive_admin) soup = BeautifulSoup(view_content) self.assertEqual( 1, len(soup.findAll('div', {'class': 'blacklist-options'})))
def test_filebug_information_type_proprietary_policy(self): # The vocabulary for information_type when filing a bug is created # correctly for a project with a proprietary sharing policy. product = self.factory.makeProduct(official_malone=True) self.factory.makeCommercialSubscription(product=product) with person_logged_in(product.owner): product.setBugSharingPolicy(BugSharingPolicy.PROPRIETARY) view = create_initialized_view(product, '+filebug', principal=product.owner) html = view.render() soup = BeautifulSoup(html) self.assertIsNotNone(soup.find('label', text="Proprietary"))
def test_filebug_view_renders_info_type_widget(self): # The info type widget is rendered for bug supervisor roles. product = self.factory.makeProduct(official_malone=True) with person_logged_in(product.owner): view = create_initialized_view(product, '+filebug', principal=product.owner) html = view.render() soup = BeautifulSoup(html) self.assertIsNone( soup.find('input', attrs={'name': 'field.security_related'})) self.assertIsNotNone( soup.find('input', attrs={'name': 'field.information_type'}))
def test_call(self): # The __call__ method sets up the widgets. markup = self.widget() self.assertIsNotNone(self.widget.core_widget) self.assertIsNotNone(self.widget.snapcraft_widget) soup = BeautifulSoup(markup) fields = soup.findAll(["input"], {"id": re.compile(".*")}) expected_ids = [ "field.auto_build_channels.core", "field.auto_build_channels.snapcraft", ] ids = [field["id"] for field in fields] self.assertContentEqual(expected_ids, ids)
def test_call(self): # The __call__ method sets up the widgets. markup = self.widget() self.assertIsNotNone(self.widget.track_widget) self.assertIsNotNone(self.widget.risks_widget) soup = BeautifulSoup(markup) fields = soup.findAll(["input"], {"id": re.compile(".*")}) expected_ids = [ "field.channels.risks.%d" % i for i in range(len(self.risks)) ] expected_ids.append("field.channels.track") expected_ids.append("field.channels.branch") ids = [field["id"] for field in fields] self.assertContentEqual(expected_ids, ids)
def test_filebug_view_renders_security_related(self): # The security_related checkbox is rendered for non bug supervisors. product = self.factory.makeProduct(official_malone=True) anyone = self.factory.makePerson() with person_logged_in(anyone): view = create_initialized_view(product, '+filebug', principal=anyone) html = view.render() soup = BeautifulSoup(html) self.assertIsNotNone( soup.find('input', attrs={'name': 'field.security_related'})) self.assertIsNone( soup.find('input', attrs={'name': 'field.information_type'}))
def test_anonymous_view_specification_with_private_subscriber(self): # A specification with a private subscriber is not rendered for anon. private_subscriber = self.factory.makeTeam( name="privateteam", visibility=PersonVisibility.PRIVATE) spec = self.factory.makeSpecification() with person_logged_in(spec.owner): spec.subscribe(private_subscriber, spec.owner) # Viewing the specification doesn't display private subscriber. url = canonical_url(spec, rootsite='blueprints') browser = self._getBrowser() browser.open(url) soup = BeautifulSoup(browser.contents) self.assertIsNone( soup.find('div', attrs={'id': 'subscriber-privateteam'}))
def test_call(self): # The __call__ method setups the widgets and the options. markup = self.widget() self.assertIsNot(None, self.widget.product_widget) self.assertTrue('personal' in self.widget.options) expected_ids = [ 'field.target.option.personal', 'field.target.option.product', 'field.target.product', ] soup = BeautifulSoup(markup) fields = soup.findAll(['input', 'select'], {'id': re.compile('.*')}) ids = [field['id'] for field in fields] self.assertContentEqual(expected_ids, ids)
def test_call(self): # The __call__ method sets up the widgets and the options. markup = self.widget() self.assertIsNotNone(self.widget.ppa_widget) self.assertIn("primary", self.widget.options) self.assertIn("ppa", self.widget.options) soup = BeautifulSoup(markup) fields = soup.findAll(["input", "select"], {"id": re.compile(".*")}) expected_ids = [ "field.archive.option.primary", "field.archive.option.ppa", "field.archive.ppa", ] ids = [field["id"] for field in fields] self.assertContentEqual(expected_ids, ids)
def test_comments_rendered(self): # If there are comments on the difference, they are rendered. ds_diff = self.factory.makeDistroSeriesDifference() person = self.factory.makePerson() with person_logged_in(person): ds_diff.addComment(person, "I'm working on this.") ds_diff.addComment(person, "Here's another comment.") view = create_initialized_view(ds_diff, '+listing-distroseries-extra') soup = BeautifulSoup(view()) self.assertEqual( 1, len(soup.findAll('pre', text="I'm working on this."))) self.assertEqual( 1, len(soup.findAll('pre', text="Here's another comment.")))
def test_remove_link_not_shown_if_no_duplicate(self): with person_logged_in(self.bug_owner): view = create_initialized_view(self.bug.default_bugtask, name="+duplicate", principal=self.bug_owner) soup = BeautifulSoup(view.render()) self.assertIsNone(soup.find(attrs={'id': 'field.actions.remove'}))
def test_ajax_remove_duplicate(self): # An ajax request to remove a duplicate returns the new bugtask table. with person_logged_in(self.bug_owner): self.bug.markAsDuplicate(self.duplicate_bug) extra = { 'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest', } form = { 'field.actions.remove': u'Remove Duplicate', } view = create_initialized_view(self.bug.default_bugtask, name="+duplicate", principal=self.bug_owner, form=form, **extra) result_html = view.render() self.assertIsNone(self.bug.duplicateof) self.assertEqual(view.request.response.getHeader('content-type'), 'text/html') soup = BeautifulSoup(result_html) table = soup.find('table', { 'id': 'affected-software', 'class': 'listing' }) self.assertIsNotNone(table)
def test_blog_posts(self): """Posts from the launchpad blog are shown when feature is enabled""" self.useFixture(FeatureFixture({'app.root_blog.enabled': True})) posts = [ self._make_blog_post(1, "A post", "Post contents.", "2002"), self._make_blog_post(2, "Another post", "More contents.", "2003"), ] calls = [] def _get_blog_posts(): calls.append('called') return posts root = getUtility(ILaunchpadRoot) with anonymous_logged_in(): view = create_initialized_view(root, 'index.html') view.getRecentBlogPosts = _get_blog_posts result = view() markup = BeautifulSoup( result, parse_only=SoupStrainer(id='homepage-blogposts')) self.assertEqual(['called'], calls) items = markup.findAll('li', 'news') # Notice about launchpad being opened is always added at the end self.assertEqual(3, len(items)) a = items[-1].find("a") self.assertEqual("Launchpad now open source", a.string.strip()) for post, item in zip(posts, items): a = item.find("a") self.assertEqual(post['link'], a["href"]) self.assertEqual(post['title'], a.string)
def test_job_notifications_display_multiple(self): job1 = self.makeJob('package_1') job2 = self.makeJob('package_2', failed=True) job3 = self.makeJob('package_3') with person_logged_in(self.archive.owner): view = create_initialized_view(self.archive, "+packages", principal=self.archive.owner) html = view.render() packages_matches = soupmatchers.HTMLContains( soupmatchers.Tag('job1', 'div', attrs={ 'class': 'pending-job', 'job_id': job1.id }), soupmatchers.Tag('job2', 'div', attrs={ 'class': 'pending-job', 'job_id': job2.id }), soupmatchers.Tag('job3', 'div', attrs={ 'class': 'pending-job', 'job_id': job3.id }), ) self.assertThat(html, packages_matches) self.assertEqual([], BeautifulSoup(html).findAll( 'span', text=re.compile('Showing 5 of .')))
def getRemoteBug(self, bug_id): """See `ExternalBugTracker`.""" # Only parse tables to save time and memory. If we didn't have # to check for application errors in the page (using # _checkForApplicationError) then we could be much more # specific than this. bug_page = BeautifulSoup(self._getPage('view.php?id=%s' % bug_id).content, convertEntities=BeautifulSoup.HTML_ENTITIES, parseOnlyThese=SoupStrainer('table')) app_error = self._checkForApplicationError(bug_page) if app_error: app_error_code, app_error_message = app_error # 1100 is ERROR_BUG_NOT_FOUND in Mantis (see # mantisbt/core/constant_inc.php). if app_error_code == '1100': return None, None else: raise BugWatchUpdateError("Mantis APPLICATION ERROR #%s: %s" % (app_error_code, app_error_message)) bug = { 'id': bug_id, 'status': self._findValueRightOfKey(bug_page, 'Status'), 'resolution': self._findValueRightOfKey(bug_page, 'Resolution') } return int(bug_id), bug