Browse Source

feat(integrations): Add Integrations Acceptance Tests. (#12632)

* Added beginnings of page objects to integrations.

* Got the add integration working.

* Revised pr so that it could be reviewed.

* combined previous efforts with mine.

* change page objects so that they no longer assert anything.

* changes get_methods to properties, removed assert button and added a few asserts where needed in test.

* removed selector since its no longer needed in page objects.

* Removed unnecessary code.

* removed assert correct page and updated js snapshots

* update js test.

* fixed prettier issues.

* Added wait until to see if I cannot avoid an error seen only in travis.

* Grabbed provider element again just in case it re-renders

* fixed missed merge

* Modified js snapshot.

* changed data-testid to data-test-id

* updated yarn test.

* updated snapshot after rebasing master.
Lauryn Brown 5 years ago
parent
commit
093b353696

+ 8 - 3
src/sentry/static/sentry/app/components/modals/integrationDetailsModal.jsx

@@ -137,7 +137,9 @@ class IntegrationDetailsModal extends React.Component {
         <Flex align="center" mb={2}>
           <PluginIcon pluginId={provider.key} size={50} />
           <Flex pl={1} align="flex-start" direction="column" justify="center">
-            <ProviderName>{t('%s Integration', provider.name)}</ProviderName>
+            <ProviderName data-test-id="provider-name">
+              {t('%s Integration', provider.name)}
+            </ProviderName>
             <Flex>
               {this.earlyAdopterLabel(provider)}
               {provider.features.length && this.featureTags(provider.features)}
@@ -165,7 +167,7 @@ class IntegrationDetailsModal extends React.Component {
           {({disabled, disabledReason}) => (
             <div className="modal-footer">
               {disabled && <DisabledNotice reason={disabledReason} />}
-              <Button size="small" onClick={closeModal}>
+              <Button data-test-id="cancel-button" size="small" onClick={closeModal}>
                 {t('Cancel')}
               </Button>
               <Access organization={organization} access={['org:integrations']}>
@@ -174,7 +176,10 @@ class IntegrationDetailsModal extends React.Component {
                     title={t('You must be an Owner, Manager or Admin to install this.')}
                     disabled={hasAccess}
                   >
-                    <AddButton disabled={disabled || !hasAccess} />
+                    <AddButton
+                      data-test-id="add-button"
+                      disabled={disabled || !hasAccess}
+                    />
                   </Tooltip>
                 )}
               </Access>

+ 7 - 1
src/sentry/static/sentry/app/views/organizationIntegrations/installedIntegration.jsx

@@ -140,6 +140,7 @@ export default class InstalledIntegration extends React.Component {
                     to={`/settings/${orgId}/integrations/${provider.key}/${
                       integration.id
                     }/`}
+                    data-test-id="integration-configure-button"
                   >
                     Configure
                   </StyledButton>
@@ -148,7 +149,12 @@ export default class InstalledIntegration extends React.Component {
             </Box>
             <Box>
               <Confirm priority="danger" disabled={!hasAccess} {...removeConfirmProps}>
-                <StyledButton disabled={!hasAccess} borderless icon="icon-trash">
+                <StyledButton
+                  disabled={!hasAccess}
+                  borderless
+                  icon="icon-trash"
+                  data-test-id="integration-remove-button"
+                >
                   Uninstall
                 </StyledButton>
               </Confirm>

+ 1 - 1
src/sentry/static/sentry/app/views/organizationIntegrations/integrationItem.jsx

@@ -23,7 +23,7 @@ export default class IntegrationItem extends React.Component {
           <IntegrationIcon size={compact ? 22 : 32} integration={integration} />
         </Box>
         <Labels compact={compact}>
-          <IntegrationName>
+          <IntegrationName data-test-id="integration-name">
             {integration.name}
             {integration.status === 'disabled' && (
               <Tooltip

+ 2 - 1
src/sentry/static/sentry/app/views/organizationIntegrations/providerRow.jsx

@@ -90,13 +90,14 @@ export default class ProviderRow extends React.Component {
         onRemove={this.props.onRemove}
         onDisable={this.props.onDisable}
         onReinstallIntegration={this.props.onReinstall}
+        data-test-id={integration.id}
       />
     ));
   }
 
   render() {
     return (
-      <PanelItem p={0} direction="column">
+      <PanelItem p={0} direction="column" data-test-id={this.props.provider.key}>
         <Flex align="center" p={2}>
           <PluginIcon size={36} pluginId={this.props.provider.key} />
           <Box px={2} flex={1}>

+ 1 - 0
tests/acceptance/page_objects/__init__.py

@@ -0,0 +1 @@
+from __future__ import absolute_import

+ 47 - 0
tests/acceptance/page_objects/base.py

@@ -0,0 +1,47 @@
+from __future__ import absolute_import
+
+
+class BasePage(object):
+    """Base class for PageObjects"""
+
+    def __init__(self, browser):
+        self.browser = browser
+
+    @property
+    def driver(self):
+        return self.browser.driver
+
+
+class BaseElement(object):
+    def __init__(self, element):
+        self.element = element
+
+
+class ButtonElement(BaseElement):
+    label_attr = 'aria-label'
+    disabled_attr = 'aria-disabled'
+
+    @property
+    def disabled(self):
+        return self.element.get_attribute(self.disabled_attr)
+
+    @property
+    def label(self):
+        return self.element.get_attribute(self.label_attr)
+
+    def click(self):
+        self.element.click()
+
+
+class ButtonWithIconElement(ButtonElement):
+    @property
+    def icon_href(self):
+        return self.element.find_element_by_tag_name('use').get_attribute('href')
+
+
+class TextBoxElement(BaseElement):
+    pass
+
+
+class ModalElement(BaseElement):
+    pass

+ 1 - 10
tests/acceptance/pages.py → tests/acceptance/page_objects/issue_details.py

@@ -2,16 +2,7 @@ from __future__ import absolute_import
 
 import json
 
-
-class BasePage(object):
-    """Base class for PageObjects"""
-
-    def __init__(self, browser):
-        self.browser = browser
-
-    @property
-    def driver(self):
-        return self.browser.driver
+from .base import BasePage
 
 
 class IssueDetailsPage(BasePage):

+ 123 - 0
tests/acceptance/page_objects/organization_integration_settings.py

@@ -0,0 +1,123 @@
+from __future__ import absolute_import
+from .base import BaseElement, BasePage, ButtonElement, ButtonWithIconElement, ModalElement
+
+
+class IntegrationProviderRowElement(BaseElement):
+    integration_name_selector = '[data-test-id="integration-name"]'
+
+    def __init__(self, provider, *args, **kwargs):
+        super(IntegrationProviderRowElement, self).__init__(
+            *args, **kwargs
+        )
+        self.provider = provider
+
+        self.install_button = ButtonWithIconElement(
+            element=self.element.find_element_by_css_selector('[role="button"]')
+        )
+
+    @classmethod
+    def get_selector(cls, provider_key):
+        return '[data-test-id="%s"]' % provider_key
+
+    @property
+    def installations(self):
+        return self.element.find_elements_by_css_selector(
+            self.integration_name_selector
+        )
+
+    def get_installation_with_name(self, name):
+        for installation in self.installations:
+            if installation.get_attribute("innerText") == name:
+                return installation
+        return None
+
+
+class InstallationElement(BaseElement):
+    configure_button_selector = '[data-test-id="integration-configure-button"]'
+    remove_button_selector = '[data-test-id="integration-remove-button"]'
+
+    def __init__(self, integration, *args, **kwargs):
+        super(InstallationElement, self).__init__(
+            *args, **kwargs
+        )
+        self.integration = integration
+        self.configure_button = ButtonWithIconElement(
+            element=self.element.find_element_by_css_selector(self.configure_button_selector),
+        )
+        self.remove_button = ButtonWithIconElement(
+            element=self.element.find_element_by_css_selector(self.remove_button_selector)
+        )
+
+
+class IntegrationDetailsModal(ModalElement):
+    title_selector = '[data-test-id="provider-name"]'
+    cancel_button_selector = '[data-test-id="cancel-button"]'
+    add_button_selector = '[data-test-id="add-button"]'
+
+    def __init__(self, provider, *args, **kwargs):
+        super(IntegrationDetailsModal, self).__init__(*args, **kwargs)
+        self.cancel_button = ButtonElement(
+            element=self.element.find_element_by_css_selector(self.cancel_button_selector),
+        )
+        self.add_button = ButtonElement(
+            element=self.element.find_element_by_css_selector(self.add_button_selector),
+        )
+        self.provider = provider
+
+    @property
+    def title(self):
+        return self.element.find_element_by_css_selector(
+            self.title_selector).get_attribute("innerText")
+
+
+class IntegrationSetupWindowElement(ModalElement):
+    title_selector = ''
+
+    def fill_in_setup_form(self, installation_data):
+        pass
+
+
+class ExampleIntegrationSetupWindowElement(IntegrationSetupWindowElement):
+    name_field_selector = 'name'
+    submit_button_selector = '[type="submit"]'
+
+    def __init__(self, *args, **kwargs):
+        super(ExampleIntegrationSetupWindowElement, self).__init__(*args, **kwargs)
+        self.name = self.element.find_element_by_name('name')
+        continue_button_element = self.element.find_element_by_css_selector(
+            self.submit_button_selector)
+        self.continue_button = ButtonElement(continue_button_element)
+
+    def fill_in_setup_form(self, installation_data):
+        self.name.send_keys(installation_data[self.name_field_selector])
+
+
+class OrganizationIntegrationSettingsPage(BasePage):
+    modal_selector = '.modal-dialog'
+
+    def __init__(self, providers=None, *args, **kwargs):
+        super(OrganizationIntegrationSettingsPage, self).__init__(*args, **kwargs)
+
+    def get_provider(self, provider):
+        selector = IntegrationProviderRowElement.get_selector(provider.key)
+        return IntegrationProviderRowElement(
+            provider=provider,
+            element=self.browser.find_element_by_css_selector(selector),
+        )
+
+    def click_install_button(self, provider_element):
+        provider_element.install_button.click()
+        self.browser.wait_until(self.modal_selector)
+        integration_details_modal = IntegrationDetailsModal(
+            provider=provider_element.provider,
+            element=self.browser,
+        )
+        return integration_details_modal
+
+    def click_through_integration_setup(
+            self, integration_details_modal, setup_window_cls, installation_data):
+        self.driver.switch_to_window(self.driver.window_handles[1])
+        integration_setup_window = setup_window_cls(element=self.browser)
+        integration_setup_window.fill_in_setup_form(installation_data)
+        integration_setup_window.continue_button.click()
+        self.driver.switch_to_window(self.driver.window_handles[0])

+ 1 - 1
tests/acceptance/test_issue_details_workflow.py

@@ -5,7 +5,7 @@ from django.utils import timezone
 
 from sentry.testutils import AcceptanceTestCase, SnubaTestCase
 from sentry.utils.samples import load_data
-from .pages import IssueDetailsPage
+from tests.acceptance.page_objects.issue_details import IssueDetailsPage
 
 
 class IssueDetailsWorkflowTest(AcceptanceTestCase, SnubaTestCase):

+ 100 - 0
tests/acceptance/test_organization_integration.py

@@ -0,0 +1,100 @@
+from __future__ import absolute_import
+
+from exam import mock
+
+from sentry.models import Integration
+from sentry.testutils import AcceptanceTestCase
+from tests.acceptance.page_objects.organization_integration_settings import (
+    OrganizationIntegrationSettingsPage, ExampleIntegrationSetupWindowElement
+)
+
+
+class OrganizationIntegrationAcceptanceTestCase(AcceptanceTestCase):
+    def setUp(self):
+        super(OrganizationIntegrationAcceptanceTestCase, self).setUp()
+        self.user = self.create_user('foo@example.com')
+        self.org = self.create_organization(
+            name='Rowdy Tiger',
+            owner=None,
+        )
+        self.team = self.create_team(organization=self.org, name='Mariachi Band')
+        self.project = self.create_project(
+            organization=self.org,
+            teams=[self.team],
+            name='Bengal',
+        )
+        self.create_member(
+            user=self.user,
+            organization=self.org,
+            role='owner',
+            teams=[self.team],
+        )
+        self.model = Integration.objects.create(
+            provider='example',
+            external_id='example',
+            name='Test Integration',
+            metadata={
+                'domain_name': 'example-test.com',
+            },
+        )
+        self.org_integration = self.model.add_organization(self.org, self.user)
+        self.login_as(self.user)
+
+        self.integration_settings_path = 'sentry-api-0-organization-integrations'
+
+    def load_page(self, url):
+        self.browser.get(url)
+        self.browser.wait_until_not('.loading-indicator')
+
+
+class OrganizationIntegrationSettingsTest(OrganizationIntegrationAcceptanceTestCase):
+    """
+    As a user(type?), I can setup, configure, and remove an integration.
+    """
+    # TODO(lb): Tests to be written
+    # test_setup_new_integration_with_repository
+    # test_setup_new_integration_with_issue_sync
+    # test_remove_existing_integration_installation
+    # test_update_legacy_integration
+    # test_user_permissions_for_integration_settings
+    # test_add_multiple_integrations_to_one_provider
+    # TODO(lb): check issues details page and see that integration shows in linked issues
+
+    def setUp(self):
+        super(OrganizationIntegrationSettingsTest, self).setUp()
+        self.org_integration_settings_path = u'/settings/{}/integrations/'.format(
+            self.organization.slug)
+
+        self.provider = mock.Mock()
+        self.provider.key = 'example'
+        self.provider.name = 'Example Installation'
+
+    def test_can_create_new_integration(self):
+        self.load_page(self.org_integration_settings_path)
+        org_settings_page = OrganizationIntegrationSettingsPage(
+            browser=self.browser
+        )
+        provider_element = org_settings_page.get_provider(self.provider)
+
+        # assert installation rather than upgrade button
+        assert provider_element.install_button.label == 'Install'
+        assert provider_element.install_button.icon_href == '#icon-circle-add'
+
+        integration_details_modal = org_settings_page.click_install_button(provider_element)
+        assert integration_details_modal.add_button.label == 'Add %s' % self.provider.key
+        assert integration_details_modal.title == '%s Integration' % self.provider.key.capitalize()
+        integration_details_modal.add_button.click()
+        org_settings_page.click_through_integration_setup(
+            integration_details_modal,
+            ExampleIntegrationSetupWindowElement,
+            {'name': self.provider.name}
+        )
+
+        # provider_element might be rerendered
+        provider_element = org_settings_page.get_provider(self.provider)
+        installation_element = provider_element.get_installation_with_name(self.provider.name)
+        assert installation_element
+        assert Integration.objects.filter(
+            provider=self.provider.key,
+            external_id=self.provider.name
+        ).exists()

Some files were not shown because too many files changed in this diff