diff --git a/caseworker/advice/conditionals.py b/caseworker/advice/conditionals.py index dc86ebbabe..1d8fb9fad7 100644 --- a/caseworker/advice/conditionals.py +++ b/caseworker/advice/conditionals.py @@ -11,5 +11,5 @@ def _get_form_field_boolean(wizard): return _get_form_field_boolean -def is_desnz_team(wizard): - return wizard.caseworker["team"]["alias"] in services.DESNZ_TEAMS +def is_fcdo_team(wizard): + return wizard.caseworker["team"]["alias"] == services.FCDO_TEAM diff --git a/caseworker/advice/forms/approval.py b/caseworker/advice/forms/approval.py index 27e18e22ec..85eacf507c 100644 --- a/caseworker/advice/forms/approval.py +++ b/caseworker/advice/forms/approval.py @@ -40,7 +40,7 @@ class Layout: TITLE = "Recommend an approval" approval_reasons = forms.CharField( - widget=forms.Textarea(attrs={"rows": 7}), + widget=forms.Textarea(attrs={"rows": 7, "class": "govuk-!-margin-top-4", "name": "approval_reasons"}), label="", error_messages={"required": "Enter a reason for approving"}, ) @@ -61,7 +61,6 @@ def __init__(self, *args, **kwargs): approval_choices, approval_text = self._picklist_to_choices(approval_reason) self.approval_text = approval_text super().__init__(*args, **kwargs) - self.fields["approval_radios"].choices = approval_choices def get_layout_fields(self): diff --git a/caseworker/advice/forms/forms.py b/caseworker/advice/forms/forms.py index d7cc103ee4..3758ccdc97 100644 --- a/caseworker/advice/forms/forms.py +++ b/caseworker/advice/forms/forms.py @@ -61,7 +61,7 @@ class GiveApprovalAdviceForm(PicklistAdviceForm): ) instructions_to_exporter = forms.CharField( widget=forms.Textarea(attrs={"rows": "3"}), - label="Add any instructions for the exporter (optional)", + label="Add instructions to the exporter, or a reporting footnote (optional)", help_text="These may be added to the licence cover letter, subject to review by the Licensing Unit.", required=False, ) diff --git a/caseworker/advice/rules.py b/caseworker/advice/rules.py index a0b34f3e7e..2f2568a340 100644 --- a/caseworker/advice/rules.py +++ b/caseworker/advice/rules.py @@ -33,8 +33,8 @@ def can_desnz_make_recommendation(user, case, queue_alias): return True -def can_desnz_make_edit(team): - return team in services.DESNZ_TEAMS +def can_ogd_make_edit(team): + return team != services.FCDO_TEAM def case_has_approval_advice(advice): @@ -44,7 +44,7 @@ def case_has_approval_advice(advice): @rules.predicate -def can_user_make_desnz_edit(request, case): +def can_user_make_edit(request, case): try: user = request.lite_user except AttributeError: @@ -52,7 +52,7 @@ def can_user_make_desnz_edit(request, case): team = user["team"]["alias"] advice = services.filter_current_user_advice(case.advice, user["id"]) - return can_desnz_make_edit(team) and case_has_approval_advice(advice) + return can_ogd_make_edit(team) and case_has_approval_advice(advice) @rules.predicate @@ -84,4 +84,4 @@ def can_user_make_recommendation(request, case): rules.add_rule("can_user_make_recommendation", is_user_allocated & can_user_make_recommendation) rules.add_rule("can_user_allocate_and_approve", can_user_make_recommendation) -rules.add_rule("can_user_make_desnz_edit", can_user_make_desnz_edit) +rules.add_rule("can_user_make_edit", can_user_make_edit) diff --git a/caseworker/advice/templates/advice/view_my_advice.html b/caseworker/advice/templates/advice/view_my_advice.html index 8595c9af9b..6773dd3a88 100644 --- a/caseworker/advice/templates/advice/view_my_advice.html +++ b/caseworker/advice/templates/advice/view_my_advice.html @@ -34,10 +34,10 @@

View recommendation

{% if my_advice %} - {% test_rule 'can_user_make_desnz_edit' request case as can_user_make_desnz_edit %} + {% test_rule 'can_user_make_edit' request case as can_user_make_edit %} {% if buttons.edit_recommendation %} - {% if can_user_make_desnz_edit %} + {% if can_user_make_edit %} Edit recommendation {% else %} Edit recommendation diff --git a/caseworker/advice/urls.py b/caseworker/advice/urls.py index b104d7a086..08ebf0becd 100644 --- a/caseworker/advice/urls.py +++ b/caseworker/advice/urls.py @@ -1,13 +1,13 @@ from django.urls import path from caseworker.advice.views import consolidate, views -from caseworker.advice.views.approval import GiveApprovalAdviceView +from caseworker.advice.views.approval import GiveApprovalAdviceView, SelectAdviceView from caseworker.advice.views.edit import EditAdviceView urlpatterns = [ path("", views.AdviceView.as_view(), name="advice_view"), path("case-details/", views.CaseDetailView.as_view(), name="case_details"), - path("select-advice/", views.SelectAdviceView.as_view(), name="select_advice"), + path("select-advice/", SelectAdviceView.as_view(), name="select_advice"), path("approve-all-legacy/", views.GiveApprovalAdviceViewLegacy.as_view(), name="approve_all_legacy"), path("approve-all/", GiveApprovalAdviceView.as_view(), name="approve_all"), path("refuse-all/", views.RefusalAdviceView.as_view(), name="refuse_all"), diff --git a/caseworker/advice/views/approval.py b/caseworker/advice/views/approval.py index 4565fb8cce..7c10c60934 100644 --- a/caseworker/advice/views/approval.py +++ b/caseworker/advice/views/approval.py @@ -1,10 +1,11 @@ from http import HTTPStatus -from caseworker.advice.conditionals import form_add_licence_conditions, is_desnz_team +from caseworker.advice.conditionals import form_add_licence_conditions, is_fcdo_team from caseworker.advice.forms.approval import ( FootnotesApprovalAdviceForm, PicklistLicenceConditionsForm, SimpleLicenceConditionsForm, RecommendAnApprovalForm, + SelectAdviceForm, ) from caseworker.advice.payloads import GiveApprovalAdvicePayloadBuilder from caseworker.advice.picklist_helpers import approval_picklist, footnote_picklist, proviso_picklist @@ -14,16 +15,37 @@ from django.urls import reverse from caseworker.advice.views.mixins import CaseContextMixin from caseworker.advice import services - from caseworker.advice.constants import AdviceSteps from core.auth.views import LoginRequiredMixin from core.decorators import expect_status +from django.views.generic import FormView + + +class SelectAdviceView(LoginRequiredMixin, CaseContextMixin, FormView): + template_name = "advice/select_advice.html" + form_class = SelectAdviceForm + + def get_success_url(self): + if self.recommendation == "approve_all": + if self.caseworker["team"]["alias"] == services.FCDO_TEAM: + return reverse("cases:approve_all_legacy", kwargs=self.kwargs) + return reverse("cases:approve_all", kwargs=self.kwargs) + else: + return reverse("cases:refuse_all", kwargs=self.kwargs) + + def form_valid(self, form): + self.recommendation = form.cleaned_data["recommendation"] + return super().form_valid(form) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + return {**context, "security_approvals_classified_display": self.security_approvals_classified_display} class BaseApprovalAdviceView(LoginRequiredMixin, CaseContextMixin, BaseSessionWizardView): condition_dict = { - AdviceSteps.RECOMMEND_APPROVAL: C(is_desnz_team), + AdviceSteps.RECOMMEND_APPROVAL: ~C(is_fcdo_team), AdviceSteps.LICENCE_CONDITIONS: C(form_add_licence_conditions(AdviceSteps.RECOMMEND_APPROVAL)), AdviceSteps.LICENCE_FOOTNOTES: C(form_add_licence_conditions(AdviceSteps.RECOMMEND_APPROVAL)), } diff --git a/caseworker/advice/views/views.py b/caseworker/advice/views/views.py index 67a258e737..9fd9e8bd71 100644 --- a/caseworker/advice/views/views.py +++ b/caseworker/advice/views/views.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from caseworker.advice.forms.approval import MoveCaseForwardForm, SelectAdviceForm +from caseworker.advice.forms.approval import MoveCaseForwardForm from caseworker.advice.forms.consolidate import ( ConsolidateApprovalForm, LUConsolidateRefusalForm, @@ -50,24 +50,6 @@ class CaseDetailView(LoginRequiredMixin, CaseContextMixin, TemplateView): template_name = "advice/case_detail_example.html" -class SelectAdviceView(LoginRequiredMixin, CaseContextMixin, FormView): - template_name = "advice/select_advice.html" - form_class = SelectAdviceForm - - def get_success_url(self): - recommendation = self.request.POST.get("recommendation") - if recommendation == "approve_all": - if self.caseworker["team"]["alias"] in services.DESNZ_TEAMS: - return reverse("cases:approve_all", kwargs=self.kwargs) - return reverse("cases:approve_all_legacy", kwargs=self.kwargs) - else: - return reverse("cases:refuse_all", kwargs=self.kwargs) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - return {**context, "security_approvals_classified_display": self.security_approvals_classified_display} - - class GiveApprovalAdviceViewLegacy(LoginRequiredMixin, CaseContextMixin, FormView): """ Form to recommend approval advice for all products on the application @@ -76,13 +58,10 @@ class GiveApprovalAdviceViewLegacy(LoginRequiredMixin, CaseContextMixin, FormVie template_name = "advice/give-approval-advice.html" def get_form(self): - if self.caseworker["team"]["alias"] == services.FCDO_TEAM: - return FCDOApprovalAdviceForm( - services.unadvised_countries(self.caseworker, self.case), - **self.get_form_kwargs(), - ) - else: - return GiveApprovalAdviceForm(**self.get_form_kwargs()) + return FCDOApprovalAdviceForm( + services.unadvised_countries(self.caseworker, self.case), + **self.get_form_kwargs(), + ) def get_form_kwargs(self): kwargs = super().get_form_kwargs() diff --git a/conf/caseworker.py b/conf/caseworker.py index 70820fdbd3..5816548d62 100644 --- a/conf/caseworker.py +++ b/conf/caseworker.py @@ -134,3 +134,4 @@ # using it here as some browsers still don't support CSP_REPORT_TO which replaces it CSP_REPORT_URI = env.tuple("CASEWORKER_CSP_REPORT_URI", default=("",)) +E2E_WAIT_MULTIPLIER = env.int("E2E_WAIT_MULTIPLIER", default=1) diff --git a/conf/exporter.py b/conf/exporter.py index 2879f0d034..c2baebadfe 100644 --- a/conf/exporter.py +++ b/conf/exporter.py @@ -129,3 +129,4 @@ # using it here as some browsers still don't support CSP_REPORT_TO which replaces it CSP_REPORT_URI = env.tuple("EXPORTER_CSP_REPORT_URI", default=("",)) +E2E_WAIT_MULTIPLIER = env.int("E2E_WAIT_MULTIPLIER", default=1) diff --git a/tests_common/functions.py b/tests_common/functions.py index 484e8ae233..c4ed6b12c4 100644 --- a/tests_common/functions.py +++ b/tests_common/functions.py @@ -7,6 +7,7 @@ from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait +from django.conf import settings def click_submit(driver: WebDriver): @@ -70,7 +71,7 @@ def select_multi_select_options(driver: WebDriver, element_selector: str, option element = driver.find_element(by=By.CSS_SELECTOR, value=element_selector) element.send_keys(option) element.send_keys(Keys.ENTER) - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located( (By.XPATH, f"//span[@class='selected-options__option-text' and contains(text(), '{option}')]") ), @@ -83,17 +84,17 @@ def click_apply_filters(driver: WebDriver): def open_case_filters(driver: WebDriver): if not driver.find_element(by=By.CLASS_NAME, value="case-filters").is_displayed(): - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, "show-filters-link")) ).click() - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.element_to_be_clickable((By.ID, "accordion-case-filters")) ).click() def try_open_filters(driver: WebDriver): if not driver.find_element(by=By.CLASS_NAME, value="lite-filter-bar").is_displayed(): - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, "show-filters-link")) ).click() @@ -115,7 +116,7 @@ def click_next_page(driver: WebDriver): def select_report_summary_subject_and_fill(driver, subject): suggestion_input_autocomplete = driver.find_element(by=By.ID, value="_report_summary_subject") suggestion_input_autocomplete.send_keys(subject) - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.text_to_be_present_in_element( (By.CSS_SELECTOR, ".lite-autocomplete__menu--visible #_report_summary_subject__option--0"), subject, @@ -128,7 +129,7 @@ def select_report_summary_subject_and_fill(driver, subject): def select_report_summary_prefix_and_fill(driver, prefix): suggestion_input_autocomplete = driver.find_element(by=By.ID, value="_report_summary_prefix") suggestion_input_autocomplete.send_keys(prefix) - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.text_to_be_present_in_element( (By.CSS_SELECTOR, ".lite-autocomplete__menu--visible #_report_summary_prefix__option--0"), prefix, diff --git a/ui_tests/caseworker/conftest.py b/ui_tests/caseworker/conftest.py index d42c1d3cc4..20dcdd35a4 100644 --- a/ui_tests/caseworker/conftest.py +++ b/ui_tests/caseworker/conftest.py @@ -284,7 +284,7 @@ def prepare_case(api_test_client, nlr): # noqa def submit_form(driver): # noqa old_page = driver.find_element(by=By.TAG_NAME, value="html") Shared(driver).click_submit() - WebDriverWait(driver, 45).until(expected_conditions.staleness_of(old_page)) + WebDriverWait(driver, 45 * settings.E2E_WAIT_MULTIPLIER).until(expected_conditions.staleness_of(old_page)) @when(parsers.parse('I click the text "{text}"')) @@ -295,7 +295,7 @@ def click_text(driver, text): # noqa @when(parsers.parse('I click "{button_text}"')) def click_button_with_text(driver, button_text): # noqa - WebDriverWait(driver, 20).until( + WebDriverWait(driver, 20 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located( ( By.XPATH, @@ -396,21 +396,28 @@ def case_list_page(driver, internal_url): # noqa @when("I go to my profile page") # noqa def get_profile_page(driver): # noqa - WebDriverWait(driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "link-profile"))).click() + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( + expected_conditions.presence_of_element_located((By.ID, "link-profile")) + ).click() @when(parsers.parse('I change my team to "{team}" and default queue to "{queue}"')) # noqa def go_to_team_edit_page(driver, team, queue): # noqa # we should already be on the profile page - WebDriverWait(driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "link-edit-team"))).click() + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( + expected_conditions.presence_of_element_located((By.ID, "link-edit-team")) + ).click() teams_page = TeamsPages(driver) teams_page.select_team_from_dropdown(team) teams_page.select_default_queue_from_dropdown(queue) functions.click_submit(driver) # Ensure we return to the profile page - WebDriverWait(driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "link-edit-team"))) + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( + expected_conditions.presence_of_element_located((By.ID, "link-edit-team")) + ) # Check that the team/queue change was applied successfully assert driver.find_element(by=By.ID, value="user-team-name").text == team + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER) assert driver.find_element(by=By.ID, value="user-default-queue").text == queue @@ -432,7 +439,9 @@ def system_queue_shown_in_dropdown(driver, queue_name): # noqa @when(parsers.parse('I switch to "{queue_name}" queue')) # noqa def switch_to_queue(driver, queue_name): # noqa driver.find_element(by=By.ID, value="link-queue").click() - WebDriverWait(driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "filter-queues"))) + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( + expected_conditions.presence_of_element_located((By.ID, "filter-queues")) + ) driver.find_element(by=By.ID, value="filter-queues").send_keys(queue_name) elements = [ item @@ -678,7 +687,7 @@ def click_edit_case_flags_link(driver, flag_name): old_page = driver.find_element(by=By.TAG_NAME, value="html") functions.click_submit(driver) - WebDriverWait(driver, 45).until(expected_conditions.staleness_of(old_page)) + WebDriverWait(driver, 45 * settings.E2E_WAIT_MULTIPLIER).until(expected_conditions.staleness_of(old_page)) @given(parsers.parse('the status is set to "{status}"')) # noqa @@ -1005,7 +1014,7 @@ def approve_case_as_team(driver, team, queue, context, internal_url, internal_in recommendation_and_decisions_page.click_make_recommendation() recommendation_and_decisions_page.click_approve_all() Shared(driver).click_submit() - recommendation_and_decisions_page.enter_reasons_for_approving("approving") + recommendation_and_decisions_page.enter_approval_reasons("approving") functions.click_submit(driver) # Submit recommmendation functions.click_submit(driver) # Move case forward click_on_created_application(driver, context, internal_url) diff --git a/ui_tests/caseworker/features/give_advice/lu_consolidate_advice.feature b/ui_tests/caseworker/features/give_advice/lu_consolidate_advice.feature index 25b69321fc..72755b3432 100644 --- a/ui_tests/caseworker/features/give_advice/lu_consolidate_advice.feature +++ b/ui_tests/caseworker/features/give_advice/lu_consolidate_advice.feature @@ -99,17 +99,20 @@ Feature: I want to record my user advice and any comments and conditions relatin And I click make recommendation And I click approve all And I click continue - And I enter "reason for approving" as the reasons for approving - And I click the text "Add a licence condition, instruction to exporter or footnote" - And I enter "MOD licence condition" as the licence condition - And I enter "instruction for exporter" as the instructions for the exporter - And I enter "reporting footnote" as the reporting footnote + And I enter "reason for approving" as the approval reasons + And I click add licence condition + And I click continue + And I enter "MOD licence condition" into the licence condition + And I click continue + And I enter "instruction for exporter" as the instructions for the exporter on the instructions step + And I enter "reporting footnote" as the reporting footnote on the instructions step And I click submit recommendation Then I see "reason for approving" as the reasons for approving And I see "MOD licence condition" as the licence condition And I see "instruction for exporter" as the instructions for the exporter And I see "reporting footnote" as the reporting footnote + ##### FCDO Sub-advisor to give advice ##### When I go to my profile page And I change my team to "FCDO" and default queue to "FCDO Cases to Review" diff --git a/ui_tests/caseworker/features/give_advice/mod_approve_advice.feature b/ui_tests/caseworker/features/give_advice/mod_approve_advice.feature index 949cd0e657..6cc0695655 100644 --- a/ui_tests/caseworker/features/give_advice/mod_approve_advice.feature +++ b/ui_tests/caseworker/features/give_advice/mod_approve_advice.feature @@ -51,16 +51,19 @@ Feature: I want to record my user advice and any comments and conditions relatin And I click make recommendation And I click approve all And I click continue - And I enter "reason for approving" as the reasons for approving - And I click the text "Add a licence condition, instruction to exporter or footnote" - And I enter "licence condition" as the licence condition - And I enter "instruction for exporter" as the instructions for the exporter - And I enter "reporting footnote" as the reporting footnote - And I click submit recommendation + And I enter "reason for approving" as the approval reasons + And I click add licence condition + And I click continue + And I enter "licence condition" into the licence condition + And I click continue + And I enter "instruction for exporter" as the instructions for the exporter on the instructions step + And I enter "reporting footnote" as the reporting footnote on the instructions step + And I click continue Then I see "reason for approving" as the reasons for approving And I see "licence condition" as the licence condition And I see "instruction for exporter" as the instructions for the exporter And I see "reporting footnote" as the reporting footnote + When I click move case forward Then I don't see previously created application diff --git a/ui_tests/caseworker/features/give_advice/mod_clear_advice.feature b/ui_tests/caseworker/features/give_advice/mod_clear_advice.feature index 2adb9f834f..39e813b961 100644 --- a/ui_tests/caseworker/features/give_advice/mod_clear_advice.feature +++ b/ui_tests/caseworker/features/give_advice/mod_clear_advice.feature @@ -19,13 +19,15 @@ Feature: I want to record my user advice and any comments and conditions relatin And I click make recommendation And I click approve all And I click continue - And I enter "reason for approving" as the reasons for approving - And I click the text "Add a licence condition, instruction to exporter or footnote" - And I enter "licence condition" as the licence condition - And I enter "instruction for exporter" as the instructions for the exporter - And I enter "reporting footnote" as the reporting footnote - And I click submit recommendation - Then I see "reason for approving" as the reasons for approving + And I enter "approval reason" as the approval reasons + And I click add licence condition + And I click continue + And I enter "licence condition" into the licence condition + And I click continue + And I enter "instruction for exporter" as the instructions for the exporter on the instructions step + And I enter "reporting footnote" as the reporting footnote on the instructions step + And I click continue + Then I see "approval reason" as the reasons for approving And I see "licence condition" as the licence condition And I see "instruction for exporter" as the instructions for the exporter And I see "reporting footnote" as the reporting footnote diff --git a/ui_tests/caseworker/features/give_advice/mod_edit_advice.feature b/ui_tests/caseworker/features/give_advice/mod_edit_advice.feature index a3fe28166a..9de6d73403 100644 --- a/ui_tests/caseworker/features/give_advice/mod_edit_advice.feature +++ b/ui_tests/caseworker/features/give_advice/mod_edit_advice.feature @@ -19,23 +19,26 @@ Feature: I want to record my user advice and any comments and conditions relatin And I click make recommendation And I click approve all And I click continue - And I enter "reason for approving" as the reasons for approving - And I click the text "Add a licence condition, instruction to exporter or footnote" - And I enter "licence condition" as the licence condition - And I enter "instruction for exporter" as the instructions for the exporter - And I enter "reporting footnote" as the reporting footnote - And I click submit recommendation - Then I see "reason for approving" as the reasons for approving + And I enter "approval reason" as the approval reasons + And I click add licence condition + And I click continue + And I enter "licence condition" into the licence condition + And I click continue + And I enter "instruction for exporter" as the instructions for the exporter on the instructions step + And I enter "reporting footnote" as the reporting footnote on the instructions step + And I click continue + Then I see "approval reason" as the reasons for approving And I see "licence condition" as the licence condition And I see "instruction for exporter" as the instructions for the exporter And I see "reporting footnote" as the reporting footnote When I click "Edit recommendation" - And I enter "reason for approving1" as the reasons for approving - And I click the text "Add a licence condition, instruction to exporter or footnote" - And I enter "licence condition1" as the licence condition - And I enter "instruction for exporter1" as the instructions for the exporter - And I enter "reporting footnote1" as the reporting footnote - And I click submit recommendation + And I enter "reason for approving1" as the approval reasons + And I click continue + And I enter "licence condition1" into the licence condition + And I click continue + And I enter "instruction for exporter1" as the instructions for the exporter on the instructions step + And I enter "reporting footnote1" as the reporting footnote on the instructions step + And I click continue Then I see "reason for approving1" as the reasons for approving And I see "licence condition1" as the licence condition And I see "instruction for exporter1" as the instructions for the exporter diff --git a/ui_tests/caseworker/features/give_advice/ogd_approve_advice.feature b/ui_tests/caseworker/features/give_advice/ogd_approve_advice.feature new file mode 100644 index 0000000000..f7a13d9605 --- /dev/null +++ b/ui_tests/caseworker/features/give_advice/ogd_approve_advice.feature @@ -0,0 +1,35 @@ +@all @internal @give_advice +Feature: I want to record my user advice and any comments and conditions relating to my recommendation + As a logged in government user working on a specific case that is assigned to me + I want to record my user advice and any comments and conditions relating to my recommendation + So that other users can see my decision and know that I have finished assessing this case + + @ogd_approve_advice + Scenario: DESNZ to approve advice journey + Given I sign in as Test UAT user + And I create standard application or standard application has been previously created + When I go to application previously created + And I assign myself to the case + And I assign the case to "DESNZ Chemical cases to review" queue + And I go to my profile page + And I change my team to "DESNZ Chemical" and default queue to "DESNZ Chemical cases to review" + And I go to my case list + And I click the application previously created + And I click the recommendations and decision tab + And I click make recommendation + And I click approve all + And I click continue + And I enter "Hello World" as the approval reasons + And I click add licence condition + And I click continue + And I enter "licence condition" into the licence condition + And I click continue + And I enter "instruction for exporter" as the instructions for the exporter on the instructions step + And I enter "reporting footnote" as the reporting footnote on the instructions step + And I click continue + Then I see "Hello World" as the reasons for approving + And I see "licence condition" as the licence condition + And I see "instruction for exporter" as the instructions for the exporter + And I see "reporting footnote" as the reporting footnote + When I click move case forward + Then I don't see previously created application diff --git a/ui_tests/caseworker/pages/BasePage.py b/ui_tests/caseworker/pages/BasePage.py index 4f12d577dc..c35cb44741 100644 --- a/ui_tests/caseworker/pages/BasePage.py +++ b/ui_tests/caseworker/pages/BasePage.py @@ -4,6 +4,7 @@ from selenium.webdriver.support.wait import WebDriverWait from tests_common import functions +from django.conf import settings class BasePage: @@ -12,7 +13,9 @@ def __init__(self, driver: WebDriver): # Wait for the cases list to load before interacting with the page if functions.element_with_id_exists(self.driver, "link-queue"): - WebDriverWait(driver, 60).until(expected_conditions.visibility_of_element_located((By.ID, "all-cases-tab"))) + WebDriverWait(driver, 60 * settings.E2E_WAIT_MULTIPLIER).until( + expected_conditions.visibility_of_element_located((By.ID, "all-cases-tab")) + ) # The case header is sticky and can often overlay elements preventing clicks, # therefore disable the stickyness of the header when running tests diff --git a/ui_tests/caseworker/pages/add_denial_records_page.py b/ui_tests/caseworker/pages/add_denial_records_page.py index c456219a13..6153a2573d 100644 --- a/ui_tests/caseworker/pages/add_denial_records_page.py +++ b/ui_tests/caseworker/pages/add_denial_records_page.py @@ -7,6 +7,7 @@ from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait +from django.conf import settings from ui_tests.caseworker.pages.BasePage import BasePage @@ -14,7 +15,7 @@ class AddDenialRecordsPage(BasePage): CSV_FILE_LOCATION = "/tmp/downloads/example-denials.csv" def download_example_csv_file(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.LINK_TEXT, "Download an example .csv file")) ).click() diff --git a/ui_tests/caseworker/pages/advice.py b/ui_tests/caseworker/pages/advice.py index 47d685c6d6..b019a469af 100644 --- a/ui_tests/caseworker/pages/advice.py +++ b/ui_tests/caseworker/pages/advice.py @@ -104,6 +104,11 @@ def click_approve_all(self): def click_refuse_all(self): self.driver.find_element(by=By.XPATH, value="//input[@type='radio' and @value='refuse_all']").click() + def click_add_licence_condition(self): + self.driver.find_element( + by=By.XPATH, value="//input[@id='id_recommend_approval-add_licence_conditions']" + ).click() + def select_country(self, country): self.driver.find_element(by=By.XPATH, value=f"//input[@type='checkbox' and @value='{country}']").click() @@ -115,6 +120,11 @@ def enter_reasons_for_approving(self, reasons): el.clear() el.send_keys(reasons) + def enter_approval_reasons(self, reasons): + el = self.driver.find_element(by=By.XPATH, value="//textarea[@name='recommend_approval-approval_reasons']") + el.clear() + el.send_keys(reasons) + def enter_reasons_for_refusal(self, reasons): el = self.driver.find_element(by=By.XPATH, value="//textarea[@name='refusal_reasons']") el.clear() @@ -125,17 +135,42 @@ def enter_refusal_note(self, note): el.clear() el.send_keys(note) - def enter_licence_condition(self, licence_condition): - el = self.driver.find_element(by=By.XPATH, value="//textarea[@name='proviso']") + def enter_licence_condition(self, licence_condition, condition_selected): + self.driver.find_element( + by=By.XPATH, value=f"//input[@type='checkbox' and @value='{condition_selected}']" + ).click() + el = self.driver.find_element(by=By.XPATH, value=f"//textarea[@name='licence_conditions-{condition_selected}']") + el.clear() + el.send_keys(licence_condition) + + def enter_licence_condition_edit(self, licence_condition): + el = self.driver.find_element(by=By.XPATH, value=f"//textarea[@name='licence_conditions-proviso']") el.clear() el.send_keys(licence_condition) def enter_instructions_for_exporter(self, instructions): - el = self.driver.find_element(by=By.XPATH, value="//textarea[@name='instructions_to_exporter']") + el = self.driver.find_element( + by=By.XPATH, value="//textarea[@name='licence_footnotes-instructions_to_exporter']" + ) el.clear() el.send_keys(instructions) def enter_reporting_footnote(self, footnote): + el = self.driver.find_element(by=By.XPATH, value="//textarea[@name='licence_footnotes-footnote_details']") + el.clear() + el.send_keys(footnote) + + def enter_licence_condition_legacy(self, licence_condition): + el = self.driver.find_element(by=By.XPATH, value="//textarea[@name='proviso']") + el.clear() + el.send_keys(licence_condition) + + def enter_instructions_for_exporter_legacy(self, instructions): + el = self.driver.find_element(by=By.XPATH, value="//textarea[@name='instructions_to_exporter']") + el.clear() + el.send_keys(instructions) + + def enter_reporting_footnote_legacy(self, footnote): el = self.driver.find_element(by=By.XPATH, value="//textarea[@name='footnote_details']") el.clear() el.send_keys(footnote) diff --git a/ui_tests/caseworker/pages/application_page.py b/ui_tests/caseworker/pages/application_page.py index efd6716fb2..c3764fbe5d 100644 --- a/ui_tests/caseworker/pages/application_page.py +++ b/ui_tests/caseworker/pages/application_page.py @@ -4,6 +4,7 @@ from selenium.webdriver.support.ui import Select from tests_common import functions +from django.conf import settings from ui_tests.caseworker.pages.BasePage import BasePage from tests_common.tools.helpers import scroll_to_element_by_id from tests_common.tools.helpers import scroll_to_element_below_header_by_id @@ -67,7 +68,7 @@ def get_case_copy_of_field_href(self): return self.driver.find_element(by=By.ID, value=self.CASE_COPY_OF_ID).get_attribute("href") def click_visible_to_exporter_checkbox(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, self.IS_VISIBLE_TO_EXPORTER_CHECKBOX_ID)) ).click() @@ -82,7 +83,7 @@ def get_text_of_case_note_field(self): return self.driver.find_element(by=By.ID, value=self.INPUT_CASE_NOTE_ID).text def click_post_note_btn(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located( (By.CSS_SELECTOR, f"#{self.BUTTON_POST_NOTE_ID}:not([disabled])") ) @@ -90,7 +91,7 @@ def click_post_note_btn(self): functions.click_submit(self.driver) def click_cancel_btn(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, self.LINK_CANCEL_NOTE_ID)) ).click() @@ -150,7 +151,7 @@ def select_a_good(self): self.driver.execute_script("arguments[0].click();", element) def click_move_case_button(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, self.MOVE_CASE_BUTTON)) ).click() @@ -241,7 +242,7 @@ def click_assign_user_button(self): self.driver.find_element(by=By.ID, value=self.ASSIGN_USER_ID).click() def click_im_done_button(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, self.BUTTON_IM_DONE_ID)) ).click() @@ -305,7 +306,7 @@ def get_matches(self, match_type): """Return a list of names that have denial matches based on the supplied match_type - one of "PARTIAL MATCH" or "EXACT MATCH". """ - table = WebDriverWait(self.driver, 30).until( + table = WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.XPATH, "//table[@id='table-denial-matches']")) ) diff --git a/ui_tests/caseworker/pages/case_list_page.py b/ui_tests/caseworker/pages/case_list_page.py index cda3687c13..5d4d9ae747 100644 --- a/ui_tests/caseworker/pages/case_list_page.py +++ b/ui_tests/caseworker/pages/case_list_page.py @@ -4,6 +4,7 @@ from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.select import Select +from django.conf import settings from ui_tests.caseworker.pages.BasePage import BasePage from ui_tests.caseworker.pages.shared import Shared from tests_common import functions @@ -154,7 +155,7 @@ def click_on_queue_title(self): self.driver.find_element(by=By.ID, value=self.LINK_CHANGE_QUEUE_ID).click() def search_for_queue(self, queue_name): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, self.QUEUE_SEARCH_BOX)) ) self.driver.find_element(by=By.ID, value=self.QUEUE_SEARCH_BOX).send_keys(queue_name) @@ -216,6 +217,6 @@ def click_checkbox_to_show_team_ecju_query_and_hidden_cases(self): return self.driver.find_element(by=By.ID, value=self.SHOW_TEAM_ECJU_AND_HIDDEN_CASES).click() def click_export_enforcement_xml(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, self.EXPORT_ENFORCEMENT_XML_BUTTON_ID)) ).click() diff --git a/ui_tests/caseworker/pages/case_page.py b/ui_tests/caseworker/pages/case_page.py index 9323081a8c..d3b205eaa8 100644 --- a/ui_tests/caseworker/pages/case_page.py +++ b/ui_tests/caseworker/pages/case_page.py @@ -1,6 +1,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait +from django.conf import settings from ui_tests.caseworker.pages.shared import Shared from ui_tests.caseworker.pages.BasePage import BasePage from tests_common import selectors @@ -128,7 +129,9 @@ def is_flag_applied(self, flag_name): self.driver.find_element(by=By.ID, value="candy-flags").click() - WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.ID, POPUP_FLAGS_ID))) + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( + expected_conditions.presence_of_element_located((By.ID, POPUP_FLAGS_ID)) + ) return flag_name in self.driver.find_element(by=By.ID, value=POPUP_FLAGS_ID).text diff --git a/ui_tests/caseworker/pages/header_page.py b/ui_tests/caseworker/pages/header_page.py index a91646d333..65330c08a0 100644 --- a/ui_tests/caseworker/pages/header_page.py +++ b/ui_tests/caseworker/pages/header_page.py @@ -1,3 +1,4 @@ +from django.conf import settings from ui_tests.caseworker.pages.BasePage import BasePage from tests_common.tools.wait import wait_until_page_is_loaded @@ -21,7 +22,7 @@ def click_lite_menu(self): self.driver.find_element(by=By.ID, value=self.MENU_BUTTON).click() def click_organisations(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.CSS_SELECTOR, self.ORGANISATIONS_LINK)) ).click() diff --git a/ui_tests/caseworker/pages/product_assessment.py b/ui_tests/caseworker/pages/product_assessment.py index ed3212dae6..afdc9e0522 100644 --- a/ui_tests/caseworker/pages/product_assessment.py +++ b/ui_tests/caseworker/pages/product_assessment.py @@ -4,6 +4,7 @@ from selenium.webdriver.support.wait import WebDriverWait from tests_common import functions +from django.conf import settings from ui_tests.caseworker.pages.BasePage import BasePage @@ -23,7 +24,7 @@ def assess_report_summary_prefix(self, ars_prefix): ars_prefix_element = self.driver.find_element(by=By.ID, value="report_summary_prefix_container") ars_prefix_input = ars_prefix_element.find_element(by=By.ID, value="_report_summary_prefix") ars_prefix_input.send_keys(ars_prefix) - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, "_report_summary_prefix__listbox")) ) @@ -39,7 +40,7 @@ def assess_report_summary_subject(self, ars_subject): ars_subject_element = self.driver.find_element(by=By.ID, value="report_summary_subject_container") ars_subject_input = ars_subject_element.find_element(by=By.ID, value="_report_summary_subject") ars_subject_input.send_keys(ars_subject) - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, "_report_summary_subject__listbox")) ) diff --git a/ui_tests/caseworker/pages/shared.py b/ui_tests/caseworker/pages/shared.py index ebde39faca..d651f66678 100644 --- a/ui_tests/caseworker/pages/shared.py +++ b/ui_tests/caseworker/pages/shared.py @@ -3,6 +3,7 @@ from selenium.webdriver.support.wait import WebDriverWait from tests_common import functions +from django.conf import settings from ui_tests.caseworker.pages.BasePage import BasePage from tests_common.tools.helpers import scroll_to_element_by_id @@ -122,14 +123,14 @@ def click_first_link_in_row(self): def expand_govuk_details(self): self.driver.find_element(by=By.CLASS_NAME, value=self.GOVUK_DETAILS).click() - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located( (By.XPATH, f"//*[contains(@class, '{self.GOVUK_DETAILS}') and ancestor::details/@open]") ) ) def try_open_filters(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.CLASS_NAME, "lite-filter-bar")) ) diff --git a/ui_tests/caseworker/step_defs/test_documents.py b/ui_tests/caseworker/step_defs/test_documents.py index 747efc190b..9581301763 100644 --- a/ui_tests/caseworker/step_defs/test_documents.py +++ b/ui_tests/caseworker/step_defs/test_documents.py @@ -3,6 +3,7 @@ from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait +from django.conf import settings from ui_tests.caseworker.pages.application_page import ApplicationPage from ui_tests.caseworker.pages.attach_document_page import AttachDocumentPage from ui_tests.caseworker.pages.documents_page import DocumentsPage @@ -28,7 +29,7 @@ def upload_a_file(driver, filename, description, tmp_path): old_page = driver.find_element(by=By.TAG_NAME, value="html") attach_document_page.click_submit_btn() - WebDriverWait(driver, 45).until(expected_conditions.staleness_of(old_page)) + WebDriverWait(driver, 45 * settings.E2E_WAIT_MULTIPLIER).until(expected_conditions.staleness_of(old_page)) @then(parsers.parse('I see a file with filename "{filename}" is uploaded')) diff --git a/ui_tests/caseworker/step_defs/test_enforcement.py b/ui_tests/caseworker/step_defs/test_enforcement.py index 82180a921f..823f54f8e1 100644 --- a/ui_tests/caseworker/step_defs/test_enforcement.py +++ b/ui_tests/caseworker/step_defs/test_enforcement.py @@ -8,6 +8,7 @@ from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait +from django.conf import settings from ui_tests.caseworker.pages.application_page import ApplicationPage from ui_tests.caseworker.pages.case_list_page import CaseListPage from ui_tests.caseworker.pages.shared import Shared @@ -146,9 +147,9 @@ def i_attach_updated_file(driver, enforcement_check_import_xml_file_path): # no upload_btn = driver.find_element(by=By.XPATH, value="//button[@type='submit']") upload_btn.click() - WebDriverWait(driver, 45).until(expected_conditions.staleness_of(old_page)) + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until(expected_conditions.staleness_of(old_page)) - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.text_to_be_present_in_element( (By.CSS_SELECTOR, ".app-snackbar__content"), "Enforcement XML imported successfully", @@ -161,7 +162,7 @@ def i_attach_updated_file(driver, enforcement_check_import_xml_file_path): # no @then(parsers.parse('the application is removed from "{queue}" queue')) def application_removed_from_queue(driver, queue): ASSIGNED_QUEUES_ID = "assigned-queues" - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, ASSIGNED_QUEUES_ID)) ).is_enabled() queue_list = driver.find_element(by=By.ID, value=ASSIGNED_QUEUES_ID).text.split("\n") diff --git a/ui_tests/caseworker/step_defs/test_give_advice/conftest.py b/ui_tests/caseworker/step_defs/test_give_advice/conftest.py index d22b7034ea..4b97614e6a 100644 --- a/ui_tests/caseworker/step_defs/test_give_advice/conftest.py +++ b/ui_tests/caseworker/step_defs/test_give_advice/conftest.py @@ -39,6 +39,11 @@ def click_refuse_all(driver): # noqa RecommendationsAndDecisionPage(driver).click_refuse_all() +@when("I click add licence condition") +def click_add_licence_condition(driver): # noqa + RecommendationsAndDecisionPage(driver).click_add_licence_condition() + + @when(parsers.parse('I select refusal criteria "{criteria}"')) def select_refusal_criteria(driver, criteria): # noqa functions.select_multi_select_options( @@ -62,6 +67,11 @@ def enter_reasons_for_approving(driver, reasons, context): # noqa RecommendationsAndDecisionPage(driver).enter_reasons_for_approving(reasons) +@when(parsers.parse('I enter "{reasons}" as the approval reasons')) +def enter_approval_reasons(driver, reasons, context): # noqa + RecommendationsAndDecisionPage(driver).enter_approval_reasons(reasons) + + @when(parsers.parse('I enter "{reasons}" as the reasons for refusal')) def enter_reasons_for_refusal(driver, reasons, context): # noqa RecommendationsAndDecisionPage(driver).enter_reasons_for_refusal(reasons) @@ -72,21 +82,41 @@ def enter_refusal_note(driver, note, context): # noqa RecommendationsAndDecisionPage(driver).enter_refusal_note(note) -@when(parsers.parse('I enter "{licence_condition}" as the licence condition')) -def enter_licence_condition(driver, licence_condition, context): # noqa - RecommendationsAndDecisionPage(driver).enter_licence_condition(licence_condition) +@when(parsers.parse('I enter "{licence_condition}" as the licence condition into the "{condition_selected}" checkbox')) +def enter_licence_condition(driver, licence_condition, condition_selected, context): # noqa + RecommendationsAndDecisionPage(driver).enter_licence_condition(licence_condition, condition_selected) -@when(parsers.parse('I enter "{instructions}" as the instructions for the exporter')) +@when(parsers.parse('I enter "{licence_condition}" into the licence condition')) +def enter_licence_condition_edit(driver, licence_condition, context): # noqa + RecommendationsAndDecisionPage(driver).enter_licence_condition_edit(licence_condition) + + +@when(parsers.parse('I enter "{instructions}" as the instructions for the exporter on the instructions step')) def enter_instructions_for_exporter(driver, instructions, context): # noqa RecommendationsAndDecisionPage(driver).enter_instructions_for_exporter(instructions) -@when(parsers.parse('I enter "{footnote}" as the reporting footnote')) +@when(parsers.parse('I enter "{footnote}" as the reporting footnote on the instructions step')) def enter_reporting_footnote(driver, footnote, context): # noqa RecommendationsAndDecisionPage(driver).enter_reporting_footnote(footnote) +@when(parsers.parse('I enter "{licence_condition}" as the licence condition')) +def enter_licence_condition_legacy(driver, licence_condition, context): # noqa + RecommendationsAndDecisionPage(driver).enter_licence_condition_legacy(licence_condition) + + +@when(parsers.parse('I enter "{instructions}" as the instructions for the exporter')) +def enter_instructions_for_exporter_legacy(driver, instructions, context): # noqa + RecommendationsAndDecisionPage(driver).enter_instructions_for_exporter_legacy(instructions) + + +@when(parsers.parse('I enter "{footnote}" as the reporting footnote')) +def enter_reporting_footnote_legacy(driver, footnote, context): # noqa + RecommendationsAndDecisionPage(driver).enter_reporting_footnote_legacy(footnote) + + @then(parsers.parse('I should see my recommendation for "{countries}" with "{reasons}"')) def should_see_recommendation(driver, countries, reasons): # noqa text = driver.find_element(by=By.XPATH, value="//main[@class='govuk-main-wrapper']//*").text diff --git a/ui_tests/caseworker/step_defs/test_give_advice/test_ogd_approve_advice.py b/ui_tests/caseworker/step_defs/test_give_advice/test_ogd_approve_advice.py new file mode 100644 index 0000000000..7d20f0486c --- /dev/null +++ b/ui_tests/caseworker/step_defs/test_give_advice/test_ogd_approve_advice.py @@ -0,0 +1,3 @@ +from pytest_bdd import scenarios + +scenarios("../../features/give_advice/ogd_approve_advice.feature", strict_gherkin=False) diff --git a/ui_tests/caseworker/step_defs/test_product_search.py b/ui_tests/caseworker/step_defs/test_product_search.py index 0559e53b97..9f505a6dd2 100644 --- a/ui_tests/caseworker/step_defs/test_product_search.py +++ b/ui_tests/caseworker/step_defs/test_product_search.py @@ -8,6 +8,7 @@ from selenium.webdriver.support.wait import WebDriverWait from tests_common import functions +from django.conf import settings from ui_tests.caseworker.pages.product_search import ProductSearchPage @@ -16,7 +17,7 @@ @when("I go to product search page") def go_to_product_search_page(driver): - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, "link-product-search")) ).click() diff --git a/ui_tests/exporter/conftest.py b/ui_tests/exporter/conftest.py index ad435c4cc6..d0b454e28f 100644 --- a/ui_tests/exporter/conftest.py +++ b/ui_tests/exporter/conftest.py @@ -1242,7 +1242,7 @@ def edit_good_details_in_application(driver, field_name, updated_value): # noqa @when(parsers.parse('I click on "{link_text}"')) # noqa def click_link_with_text(driver, link_text): # noqa - WebDriverWait(driver, 30).until( + WebDriverWait(driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located( ( By.LINK_TEXT, diff --git a/ui_tests/exporter/pages/shared.py b/ui_tests/exporter/pages/shared.py index 24a9b074f5..ba9a9646a1 100644 --- a/ui_tests/exporter/pages/shared.py +++ b/ui_tests/exporter/pages/shared.py @@ -1,3 +1,4 @@ +from django.conf import settings from ui_tests.exporter.pages.BasePage import BasePage from selenium.webdriver.common.by import By @@ -33,7 +34,7 @@ def get_table_rows(self): return self.driver.find_elements_by_css_selector(self.GOV_TABLE_ROW) def get_text_of_organisation_heading(self): - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 30 * settings.E2E_WAIT_MULTIPLIER).until( expected_conditions.presence_of_element_located((By.ID, self.ORG_NAME_HEADING_ID)) ) diff --git a/unit_tests/caseworker/advice/test_rules.py b/unit_tests/caseworker/advice/test_rules.py index 87922f445d..417489d76d 100644 --- a/unit_tests/caseworker/advice/test_rules.py +++ b/unit_tests/caseworker/advice/test_rules.py @@ -189,7 +189,7 @@ def test_can_user_allocate_and_approve(mock_gov_user, data_fake_queue, data_stan assert rules.test_rule("can_user_allocate_and_approve", request, case) -def test_can_user_make_desnz_edit_valid( +def test_can_user_make_edit_valid( mock_gov_user, data_fake_queue, data_assigned_case, standard_case_with_advice, mocker ): mock_gov_user["user"]["team"]["alias"] = services.DESNZ_TEAMS[0] @@ -198,44 +198,44 @@ def test_can_user_make_desnz_edit_valid( mocker.patch( "caseworker.advice.rules.services.filter_current_user_advice", return_value=standard_case_with_advice["advice"] ) - assert rules.test_rule("can_user_make_desnz_edit", request, data_assigned_case) + assert rules.test_rule("can_user_make_edit", request, data_assigned_case) -def test_can_user_make_desnz_edit_invalid_advice(mock_gov_user, data_fake_queue, data_assigned_case): +def test_can_user_make_edit_invalid_advice(mock_gov_user, data_fake_queue, data_assigned_case): mock_gov_user["user"]["team"]["alias"] = services.DESNZ_TEAMS[0] data_fake_queue["alias"] = services.DESNZ_CHEMICAL_CASES_TO_REVIEW - data_assigned_case request = get_mock_request(mock_gov_user["user"], data_fake_queue) - assert not rules.test_rule("can_user_make_desnz_edit", request, data_assigned_case) + assert not rules.test_rule("can_user_make_edit", request, data_assigned_case) -def test_can_user_make_desnz_edit_invalid_user( +def test_can_user_make_edit_invalid_user( mock_gov_user, data_fake_queue, data_assigned_case, standard_case_with_advice, mocker ): - data_fake_queue["alias"] = services.DESNZ_CHEMICAL_CASES_TO_REVIEW + mock_gov_user["user"]["team"]["alias"] = services.FCDO_TEAM + data_fake_queue["alias"] = services.FCDO_CASES_TO_REVIEW_QUEUE request = get_mock_request(mock_gov_user["user"], data_fake_queue) mocker.patch( "caseworker.advice.rules.services.filter_current_user_advice", return_value=standard_case_with_advice["advice"] ) - assert not rules.test_rule("can_user_make_desnz_edit", request, data_assigned_case) + assert not rules.test_rule("can_user_make_edit", request, data_assigned_case) -def test_can_user_make_desnz_edit_invalid_advice_and_user(mock_gov_user, data_fake_queue, data_assigned_case): - data_fake_queue["alias"] = services.DESNZ_CHEMICAL_CASES_TO_REVIEW - data_assigned_case +def test_can_user_make_edit_invalid_advice_and_user(mock_gov_user, data_fake_queue, data_assigned_case): + mock_gov_user["user"]["team"]["alias"] = services.FCDO_TEAM + data_fake_queue["alias"] = services.FCDO_CASES_TO_REVIEW_QUEUE request = get_mock_request(mock_gov_user["user"], data_fake_queue) - assert not rules.test_rule("can_user_make_desnz_edit", request, data_assigned_case) + assert not rules.test_rule("can_user_make_edit", request, data_assigned_case) -def test_can_user_make_desnz_edit_request_missing_attributes(mock_gov_user, data_fake_queue, data_standard_case): +def test_can_user_make_edit_request_missing_attributes(mock_gov_user, data_fake_queue, data_standard_case): case = Case(data_standard_case["case"]) request = None - assert not advice_rules.can_user_make_desnz_edit(request, case) + assert not advice_rules.can_user_make_edit(request, case) -def test_can_user_make_desnz_edit_user_not_allocated(mock_gov_user, data_fake_queue, data_standard_case): +def test_can_user_make_edit_user_not_allocated(mock_gov_user, data_fake_queue, data_standard_case): case = Case(data_standard_case["case"]) request = get_mock_request(mock_gov_user["user"], data_fake_queue) - assert not rules.test_rule("can_user_make_desnz_edit", request, case) + assert not rules.test_rule("can_user_make_edit", request, case) diff --git a/unit_tests/caseworker/advice/views/test_edit_advice.py b/unit_tests/caseworker/advice/views/test_edit_advice.py index 7dc8fc90c0..437b66cb7e 100644 --- a/unit_tests/caseworker/advice/views/test_edit_advice.py +++ b/unit_tests/caseworker/advice/views/test_edit_advice.py @@ -20,7 +20,19 @@ def url(data_queue, data_standard_case): ) -def test_edit_approve_advice_post(authorized_client, requests_mock, data_standard_case, standard_case_with_advice, url): +@pytest.fixture +def url_approve(data_queue, data_standard_case): + return reverse(f"cases:edit_advice", kwargs={"queue_pk": data_queue["id"], "pk": data_standard_case["case"]["id"]}) + + +@pytest.fixture +def post_to_step(post_to_step_factory, url_approve): + return post_to_step_factory(url_approve) + + +def test_edit_approve_advice_post_legacy( + authorized_client, requests_mock, data_standard_case, standard_case_with_advice, url +): user_advice_create_url = f"/cases/{data_standard_case['case']['id']}/user-advice/" requests_mock.post(user_advice_create_url, json={}) case_data = deepcopy(data_standard_case) @@ -107,6 +119,210 @@ def test_edit_approve_advice_post(authorized_client, requests_mock, data_standar ] +def test_edit_approve_advice_post( + authorized_client, requests_mock, mock_post_advice, data_standard_case, standard_case_with_advice, post_to_step +): + user_advice_create_url = f"/cases/{data_standard_case['case']['id']}/user-advice/" + case_data = deepcopy(data_standard_case) + case_data["case"]["data"]["goods"] = standard_case_with_advice["data"]["goods"] + case_data["case"]["advice"] = standard_case_with_advice["advice"] + + requests_mock.get(client._build_absolute_uri(f"/cases/{data_standard_case['case']['id']}"), json=case_data) + requests_mock.get( + client._build_absolute_uri(f"/gov_users/{data_standard_case['case']['id']}"), + json={"user": {"id": "58e62718-e889-4a01-b603-e676b794b394"}}, + ) + + data = {"approval_reasons": "meets the requirements updated", "add_licence_conditions": False} + response = post_to_step( + AdviceSteps.RECOMMEND_APPROVAL, + data, + ) + assert response.status_code == 302 + history = [item for item in requests_mock.request_history if user_advice_create_url in item.url] + assert len(history) == 1 + history = history[0] + assert history.method == "POST" + assert history.json() == [ + { + "denial_reasons": [], + "end_user": "95d3ea36-6ab9-41ea-a744-7284d17b9cc5", + "footnote": "", + "footnote_required": False, + "note": "", + "proviso": "", + "text": "meets the requirements updated", + "type": "approve", + }, + { + "consignee": "cd2263b4-a427-4f14-8552-505e1d192bb8", + "denial_reasons": [], + "footnote": "", + "footnote_required": False, + "note": "", + "proviso": "", + "text": "meets the requirements updated", + "type": "approve", + }, + { + "ultimate_end_user": "9f077b3c-6116-4111-b9a0-b2491198aa72", + "denial_reasons": [], + "footnote": "", + "footnote_required": False, + "note": "", + "proviso": "", + "text": "meets the requirements updated", + "type": "approve", + }, + { + "denial_reasons": [], + "footnote": "", + "footnote_required": False, + "note": "", + "proviso": "", + "text": "meets the requirements updated", + "third_party": "95c2d6b7-5cfd-47e8-b3c8-dc76e1ac9747", + "type": "approve", + }, + { + "denial_reasons": [], + "footnote": "", + "footnote_required": False, + "good": "9fbffa7f-ef50-402e-93ac-2f3f37d09030", + "note": "", + "proviso": "", + "text": "meets the requirements updated", + "type": "approve", + }, + { + "denial_reasons": [], + "footnote": "", + "footnote_required": False, + "good": "d4feac1e-851d-41a5-b833-eb28addb8547", + "note": "", + "proviso": "", + "text": "", + "type": "no_licence_required", + }, + ] + + +def test_give_approval_advice_post_valid_add_conditional( + authorized_client, + requests_mock, + data_standard_case, + mock_approval_reason, + mock_proviso, + mock_footnote_details, + mock_post_advice, + standard_case_with_advice, + post_to_step, + beautiful_soup, + mocker, +): + case_data = deepcopy(data_standard_case) + case_data["case"]["data"]["goods"] = standard_case_with_advice["data"]["goods"] + case_data["case"]["advice"] = standard_case_with_advice["advice"] + + requests_mock.get(client._build_absolute_uri(f"/cases/{data_standard_case['case']['id']}"), json=case_data) + requests_mock.get( + client._build_absolute_uri(f"/gov_users/{data_standard_case['case']['id']}"), + json={"user": {"id": "58e62718-e889-4a01-b603-e676b794b394"}}, + ) + + response = post_to_step( + AdviceSteps.RECOMMEND_APPROVAL, + {"approval_reasons": "reason updated", "add_licence_conditions": True}, + ) + assert response.status_code == 200 + soup = beautiful_soup(response.content) + # redirected to next form + header = soup.find("h1") + assert header.text == "Add licence conditions (optional)" + + add_licence_condition_response = post_to_step( + AdviceSteps.LICENCE_CONDITIONS, + {"proviso": "proviso updated"}, + ) + assert add_licence_condition_response.status_code == 200 + soup = beautiful_soup(add_licence_condition_response.content) + # redirected to next form + header = soup.find("h1") + assert header.text == "Add instructions to the exporter, or a reporting footnote (optional)" + + add_instructions_response = post_to_step( + AdviceSteps.LICENCE_FOOTNOTES, + {"instructions_to_exporter": "instructions updated", "footnote_details": "footnotes updated"}, + ) + assert add_instructions_response.status_code == 302 + history = mock_post_advice.request_history + assert len(history) == 1 + history = history[0] + assert history.method == "POST" + assert history.json() == [ + { + "type": "proviso", + "text": "reason updated", + "proviso": "proviso updated", + "note": "instructions updated", + "footnote_required": True, + "footnote": "footnotes updated", + "end_user": "95d3ea36-6ab9-41ea-a744-7284d17b9cc5", + "denial_reasons": [], + }, + { + "type": "proviso", + "text": "reason updated", + "proviso": "proviso updated", + "note": "instructions updated", + "footnote_required": True, + "footnote": "footnotes updated", + "consignee": "cd2263b4-a427-4f14-8552-505e1d192bb8", + "denial_reasons": [], + }, + { + "type": "proviso", + "text": "reason updated", + "proviso": "proviso updated", + "note": "instructions updated", + "footnote_required": True, + "footnote": "footnotes updated", + "ultimate_end_user": "9f077b3c-6116-4111-b9a0-b2491198aa72", + "denial_reasons": [], + }, + { + "type": "proviso", + "text": "reason updated", + "proviso": "proviso updated", + "note": "instructions updated", + "footnote_required": True, + "footnote": "footnotes updated", + "third_party": "95c2d6b7-5cfd-47e8-b3c8-dc76e1ac9747", + "denial_reasons": [], + }, + { + "type": "proviso", + "text": "reason updated", + "proviso": "proviso updated", + "note": "instructions updated", + "footnote_required": True, + "footnote": "footnotes updated", + "good": "9fbffa7f-ef50-402e-93ac-2f3f37d09030", + "denial_reasons": [], + }, + { + "type": "no_licence_required", + "text": "", + "proviso": "", + "note": "", + "footnote_required": False, + "footnote": "", + "good": "d4feac1e-851d-41a5-b833-eb28addb8547", + "denial_reasons": [], + }, + ] + + def test_edit_refuse_advice_post( authorized_client, requests_mock, @@ -139,19 +355,19 @@ def test_edit_refuse_advice_post( assert history.method == "POST" assert history.json() == [ { - "denial_reasons": ["3", "4", "5", "5a", "5b"], - "end_user": "95d3ea36-6ab9-41ea-a744-7284d17b9cc5", - "footnote_required": False, - "text": "doesn't meet the requirement", "type": "refuse", + "text": "doesn't meet the requirement", + "footnote_required": False, + "end_user": "95d3ea36-6ab9-41ea-a744-7284d17b9cc5", + "denial_reasons": ["3", "4", "5", "5a", "5b"], "is_refusal_note": False, }, { + "type": "refuse", + "text": "doesn't meet the requirement", + "footnote_required": False, "consignee": "cd2263b4-a427-4f14-8552-505e1d192bb8", "denial_reasons": ["3", "4", "5", "5a", "5b"], - "footnote_required": False, - "text": "doesn't meet the requirement", - "type": "refuse", "is_refusal_note": False, }, { @@ -163,30 +379,30 @@ def test_edit_refuse_advice_post( "is_refusal_note": False, }, { - "denial_reasons": ["3", "4", "5", "5a", "5b"], - "footnote_required": False, + "type": "refuse", "text": "doesn't meet the requirement", + "footnote_required": False, "third_party": "95c2d6b7-5cfd-47e8-b3c8-dc76e1ac9747", - "type": "refuse", + "denial_reasons": ["3", "4", "5", "5a", "5b"], "is_refusal_note": False, }, { - "denial_reasons": ["3", "4", "5", "5a", "5b"], + "type": "refuse", + "text": "doesn't meet the requirement", "footnote_required": False, "good": "9fbffa7f-ef50-402e-93ac-2f3f37d09030", - "text": "doesn't meet the requirement", - "type": "refuse", + "denial_reasons": ["3", "4", "5", "5a", "5b"], "is_refusal_note": False, }, { - "denial_reasons": [], - "footnote": "", + "type": "no_licence_required", + "text": "", + "proviso": "", + "note": "", "footnote_required": False, + "footnote": "", "good": "d4feac1e-851d-41a5-b833-eb28addb8547", - "note": "", - "proviso": "", - "text": "", - "type": "no_licence_required", + "denial_reasons": [], }, ] @@ -209,16 +425,6 @@ def test_edit_refuse_advice_get( assert response.context["current_user"] == mock_gov_user["user"] -@pytest.fixture -def url_desnz(data_queue, data_standard_case): - return reverse(f"cases:edit_advice", kwargs={"queue_pk": data_queue["id"], "pk": data_standard_case["case"]["id"]}) - - -@pytest.fixture -def post_to_step(post_to_step_factory, url_desnz): - return post_to_step_factory(url_desnz) - - def test_DESNZ_give_approval_advice_post_valid( authorized_client, requests_mock, @@ -230,7 +436,6 @@ def test_DESNZ_give_approval_advice_post_valid( mock_post_advice, standard_case_with_advice, post_to_step, - beautiful_soup, mocker, ): get_gov_user_value = ( diff --git a/unit_tests/caseworker/advice/views/test_give_approval_advice_view.py b/unit_tests/caseworker/advice/views/test_give_approval_advice_view.py index 5a2a4504bd..adc8fcb87b 100644 --- a/unit_tests/caseworker/advice/views/test_give_approval_advice_view.py +++ b/unit_tests/caseworker/advice/views/test_give_approval_advice_view.py @@ -21,19 +21,115 @@ def url(data_queue, data_standard_case): ) +@pytest.fixture +def url_approve(data_queue, data_standard_case): + return reverse("cases:approve_all", kwargs={"queue_pk": data_queue["id"], "pk": data_standard_case["case"]["id"]}) + + +@pytest.fixture +def post_to_step(post_to_step_factory, url_approve): + return post_to_step_factory(url_approve) + + def test_give_approval_advice_get(authorized_client, url): response = authorized_client.get(url) assert response.status_code == 200 -def test_select_advice_post(authorized_client, requests_mock, data_standard_case, url): - requests_mock.post(f"/cases/{data_standard_case['case']['id']}/user-advice/", json={}) - - data = {"approval_reasons": "meets the requirements", "instructions_to_exporter": "no specific instructions"} - response = authorized_client.post(url, data=data) +def test_approval_advice_post_valid( + authorized_client, + data_standard_case, + url, + mock_approval_reason, + mock_proviso, + mock_footnote_details, + mock_post_advice, + post_to_step, + beautiful_soup, +): + response = post_to_step( + AdviceSteps.RECOMMEND_APPROVAL, + {"approval_reasons": "Data"}, + ) assert response.status_code == 302 +def test_approval_advice_post_valid_add_conditional( + authorized_client, + data_standard_case, + url, + mock_approval_reason, + mock_proviso, + mock_footnote_details, + mock_post_advice, + post_to_step, + beautiful_soup, +): + response = post_to_step( + AdviceSteps.RECOMMEND_APPROVAL, + {"approval_reasons": "reason", "add_licence_conditions": True}, + ) + assert response.status_code == 200 + soup = beautiful_soup(response.content) + # redirected to next form + header = soup.find("h1") + assert header.text == "Add licence conditions (optional)" + + add_LC_response = post_to_step( + AdviceSteps.LICENCE_CONDITIONS, + {"proviso": "proviso"}, + ) + assert add_LC_response.status_code == 200 + soup = beautiful_soup(add_LC_response.content) + # redirected to next form + header = soup.find("h1") + assert header.text == "Add instructions to the exporter, or a reporting footnote (optional)" + + add_instructions_response = post_to_step( + AdviceSteps.LICENCE_FOOTNOTES, + {"instructions_to_exporter": "instructions", "footnote_details": "footnotes"}, + ) + assert add_instructions_response.status_code == 302 + + +def test_approval_advice_post_valid_add_conditional_optional( + authorized_client, + data_standard_case, + url, + mock_approval_reason, + mock_proviso, + mock_footnote_details, + mock_post_advice, + post_to_step, + beautiful_soup, +): + response = post_to_step( + AdviceSteps.RECOMMEND_APPROVAL, + {"approval_reasons": "reason", "add_licence_conditions": True}, + ) + assert response.status_code == 200 + soup = beautiful_soup(response.content) + # redirected to next form + header = soup.find("h1") + assert header.text == "Add licence conditions (optional)" + + add_LC_response = post_to_step( + AdviceSteps.LICENCE_CONDITIONS, + {}, + ) + assert add_LC_response.status_code == 200 + soup = beautiful_soup(add_LC_response.content) + # redirected to next form + header = soup.find("h1") + assert header.text == "Add instructions to the exporter, or a reporting footnote (optional)" + + add_instructions_response = post_to_step( + AdviceSteps.LICENCE_FOOTNOTES, + {}, + ) + assert add_instructions_response.status_code == 302 + + @mock.patch("caseworker.advice.views.mixins.get_gov_user") def test_fco_give_approval_advice_get(mock_get_gov_user, authorized_client, url): mock_get_gov_user.return_value = ( @@ -118,16 +214,6 @@ def test_fcdo_give_approval_advice_post( assert response.status_code == expected_status_code -@pytest.fixture -def url_desnz(data_queue, data_standard_case): - return reverse("cases:approve_all", kwargs={"queue_pk": data_queue["id"], "pk": data_standard_case["case"]["id"]}) - - -@pytest.fixture -def post_to_step(post_to_step_factory, url_desnz): - return post_to_step_factory(url_desnz) - - @mock.patch("caseworker.advice.views.mixins.get_gov_user") def test_DESNZ_give_approval_advice_post_valid( mock_get_gov_user, diff --git a/unit_tests/caseworker/advice/views/test_select_advice_view.py b/unit_tests/caseworker/advice/views/test_select_advice_view.py index 58155c632a..23e73e5e6d 100644 --- a/unit_tests/caseworker/advice/views/test_select_advice_view.py +++ b/unit_tests/caseworker/advice/views/test_select_advice_view.py @@ -21,10 +21,33 @@ def test_select_advice_get(authorized_client, url): assert response.status_code == 200 +@pytest.mark.parametrize("recommendation, redirect", [("approve_all", "approve-all"), ("refuse_all", "refuse-all")]) +def test_select_advice_post(authorized_client, url, recommendation, redirect, data_standard_case): + response = authorized_client.post(url, data={"recommendation": recommendation}) + assert response.status_code == 302 + assert ( + response.url + == f'/queues/00000000-0000-0000-0000-000000000001/cases/{data_standard_case["case"]["id"]}/advice/{redirect}/' + ) + + @pytest.mark.parametrize( "recommendation, redirect", [("approve_all", "approve-all-legacy"), ("refuse_all", "refuse-all")] ) -def test_select_advice_post(authorized_client, url, recommendation, redirect, data_standard_case): +def test_select_advice_post_fcdo(authorized_client, url, recommendation, redirect, data_standard_case, mocker): + get_gov_user_value = ( + { + "user": { + "team": { + "id": "56273dd4-4634-4ad7-a782-e480f85a85a9", + "name": "FCDO", + "alias": services.FCDO_TEAM, + } + } + }, + None, + ) + mocker.patch("caseworker.advice.views.mixins.get_gov_user", return_value=get_gov_user_value) response = authorized_client.post(url, data={"recommendation": recommendation}) assert response.status_code == 302 assert (