diff options
Diffstat (limited to 'qa/qa/specs/features/browser_ui/3_create')
12 files changed, 312 insertions, 225 deletions
diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb deleted file mode 100644 index 71415c4bb57..00000000000 --- a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe 'Create', quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839', - type: :test_environment, - only: { job: 'review-qa-*' } - } do - context 'Design Management' do - let(:issue) { Resource::Issue.fabricate_via_api! } - let(:design_filename) { 'banana_sample.gif' } - let(:design) { File.absolute_path(File.join('qa', 'fixtures', 'designs', design_filename)) } - let(:annotation) { "This design is great!" } - - before do - Flow::Login.sign_in - end - - it 'user adds a design and annotates it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do - issue.visit! - - Page::Project::Issue::Show.perform do |issue| - issue.add_design(design) - issue.click_design(design_filename) - issue.add_annotation(annotation) - - expect(issue).to have_annotation(annotation) - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb deleted file mode 100644 index 9b969f563a2..00000000000 --- a/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe 'Create', quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839', - type: :test_environment, - only: { job: 'review-qa-*' } - } do - context 'Design Management' do - let(:first_design) { Resource::Design.fabricate! } - - let(:second_design) do - Resource::Design.fabricate! do |design| - design.issue = first_design.issue - design.filename = 'values.png' - end - end - - let(:third_design) do - Resource::Design.fabricate_via_browser_ui! do |design| - design.issue = second_design.issue - design.filename = 'testfile.png' - end - end - - before do - Flow::Login.sign_in - end - - it 'user archives a design', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347713' do - third_design.issue.visit! - - Page::Project::Issue::Show.perform do |issue| - issue.select_design(third_design.filename) - - issue.archive_selected_designs - - expect(issue).not_to have_design(third_design.filename) - expect(issue).to have_design(first_design.filename) - expect(issue).to have_design(second_design.filename) - end - - Page::Project::Issue::Show.perform do |issue| - issue.select_design(second_design.filename) - issue.select_design(first_design.filename) - - issue.archive_selected_designs - - expect(issue).not_to have_design(first_design.filename) - expect(issue).not_to have_design(second_design.filename) - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb deleted file mode 100644 index 6a0c51245ad..00000000000 --- a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe 'Create', quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839', - type: :test_environment, - only: { job: 'review-qa-*' } - } do - context 'Design Management' do - let(:design) do - Resource::Design.fabricate_via_browser_ui! do |design| - design.filename = 'testfile.png' - end - end - - before do - Flow::Login.sign_in - end - - it( - 'user adds a design and modifies it', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347712' - ) do - design.issue.visit! - - Page::Project::Issue::Show.perform do |issue| - expect(issue).to have_created_icon - end - - Page::Project::Issue::Show.perform do |issue| - issue.update_design(design.filename) - expect(issue).to have_modified_icon - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb index ea531d84634..4bfd253c992 100644 --- a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb @@ -1,8 +1,23 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', :requires_admin, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/195179', type: :flaky } do + RSpec.describe 'Create', :requires_admin, :skip_live_env, except: { job: 'review-qa-*' } do describe 'Jenkins integration' do + let(:jenkins_server) { Service::DockerRun::Jenkins.new } + + let(:jenkins_client) do + Vendor::Jenkins::Client.new( + jenkins_server.host_name, + port: jenkins_server.port, + user: Runtime::Env.jenkins_admin_username, + password: Runtime::Env.jenkins_admin_password + ) + end + + let(:jenkins_project_name) { "gitlab_jenkins_#{SecureRandom.hex(5)}" } + + let(:connection_name) { 'gitlab-connection' } + let(:project_name) { "project_with_jenkins_#{SecureRandom.hex(4)}" } let(:project) do @@ -13,97 +28,82 @@ module QA end end - before do - jenkins_server = run_jenkins_server + let(:access_token) do + Runtime::Env.personal_access_token ||= fabricate_access_token + end - Vendor::Jenkins::Page::Base.host = jenkins_server.host_address + before do + toggle_local_requests(true) + jenkins_server.register! - Runtime::Env.personal_access_token ||= fabricate_personal_access_token + Support::Waiter.wait_until(max_duration: 30, reload_page: false, retry_on_exception: true) do + jenkins_client.ready? + end - allow_requests_to_local_networks + configure_gitlab_jenkins + end - setup_jenkins + after do + jenkins_server&.remove! + toggle_local_requests(false) end it 'integrates and displays build status for MR pipeline in GitLab', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347788' do - login_to_gitlab + setup_project_integration - setup_project_integration_with_jenkins + jenkins_integration = project.find_integration('jenkins') + expect(jenkins_integration).not_to be(nil), 'Jenkins integration did not save' + expect(jenkins_integration[:active]).to be(true), 'Jenkins integration is not active' - expect(page).to have_text("Jenkins settings saved and active.") + job = create_jenkins_job - QA::Support::Retrier.retry_on_exception do - Resource::Repository::ProjectPush.fabricate! do |push| - push.project = project - push.new_branch = false - push.file_name = "file_#{SecureRandom.hex(4)}.txt" - end - - Vendor::Jenkins::Page::LastJobConsole.perform do |job_console| - job_console.job_name = project_name + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.new_branch = false + push.file_name = "file_#{SecureRandom.hex(4)}.txt" + end - job_console.visit! + Support::Waiter.wait_until(max_duration: 60, raise_on_failure: false, reload_page: false) do + job.status == :success + end - Support::Waiter.wait_until(sleep_interval: 2, reload_page: page) do - job_console.has_successful_build? && job_console.no_failed_status_update? - end - end + expect(job.status).to eql(:success), "Build failed or is not found: #{job.log}" - project.visit! + project.visit! - Flow::Pipeline.visit_latest_pipeline + Flow::Pipeline.visit_latest_pipeline - Page::Project::Pipeline::Show.perform do |show| - expect(show).to have_build('jenkins', status: :success, wait: 15) - end + Page::Project::Pipeline::Show.perform do |show| + expect(show).to have_build('jenkins', status: :success, wait: 15) end end - after do - remove_jenkins_server - end + private - def setup_jenkins - Vendor::Jenkins::Page::Login.perform do |login_page| - login_page.visit! - login_page.login - end - - token_description = "token-#{SecureRandom.hex(8)}" - - Vendor::Jenkins::Page::NewCredentials.perform do |new_credentials| - new_credentials.visit_and_set_gitlab_api_token(Runtime::Env.personal_access_token, token_description) - end - - Vendor::Jenkins::Page::Configure.perform do |configure| - configure.visit_and_setup_gitlab_connection(patch_host_name(Runtime::Scenario.gitlab_address, 'gitlab'), token_description) do - configure.click_test_connection - expect(configure).to have_success - end - end + def setup_project_integration + login_to_gitlab - Vendor::Jenkins::Page::NewJob.perform do |new_job| - new_job.visit_and_create_new_job_with_name(project_name) - end + project.visit! - Vendor::Jenkins::Page::ConfigureJob.perform do |configure_job| - configure_job.job_name = project_name - configure_job.configure(scm_url: patch_host_name(project.repository_http_location.git_uri, 'gitlab')) - end - end + Page::Project::Menu.perform(&:click_project) + Page::Project::Menu.perform(&:go_to_integrations_settings) + Page::Project::Settings::Integrations.perform(&:click_jenkins_ci_link) - def run_jenkins_server - Service::DockerRun::Jenkins.new.tap do |runner| - runner.pull - runner.register! + QA::Page::Project::Settings::Services::Jenkins.perform do |jenkins| + jenkins.setup_service_with( + jenkins_url: patch_host_name(jenkins_server.host_address, 'jenkins-server'), + project_name: jenkins_project_name, + username: jenkins_server.username, + password: jenkins_server.password + ) end end - def remove_jenkins_server - Service::DockerRun::Jenkins.new.remove! + def login_to_gitlab + Flow::Login.sign_in end - def fabricate_personal_access_token + def fabricate_access_token login_to_gitlab token = Resource::PersonalAccessToken.fabricate!.token @@ -111,8 +111,23 @@ module QA token end - def login_to_gitlab - Flow::Login.sign_in + def create_jenkins_job + jenkins_client.create_job jenkins_project_name do |job| + job.gitlab_connection = connection_name + job.description = 'Just a job' + job.repo_url = patch_host_name(project.repository_http_location.git_uri, 'gitlab') + job.shell_command = 'sleep 5' + end + end + + def configure_gitlab_jenkins + jenkins_client.configure_gitlab_plugin( + patch_host_name(Runtime::Scenario.gitlab_address, 'gitlab'), + connection_name: connection_name, + access_token: access_token, + read_timeout: 20, + connection_timeout: 10 + ) end def patch_host_name(host_name, container_name) @@ -122,32 +137,8 @@ module QA host_name.gsub('localhost', ip_address) end - def setup_project_integration_with_jenkins - project.visit! - - Page::Project::Menu.perform(&:click_project) - Page::Project::Menu.perform(&:go_to_integrations_settings) - Page::Project::Settings::Integrations.perform(&:click_jenkins_ci_link) - - QA::Page::Project::Settings::Services::Jenkins.perform do |jenkins| - jenkins.setup_service_with(jenkins_url: patch_host_name(Vendor::Jenkins::Page::Base.host, 'jenkins-server'), - project_name: project_name) - end - end - - def allow_requests_to_local_networks - Page::Main::Menu.perform(&:sign_out_if_signed_in) - Flow::Login.sign_in_as_admin - Page::Main::Menu.perform(&:go_to_admin_area) - Page::Admin::Menu.perform(&:go_to_network_settings) - - Page::Admin::Settings::Network.perform do |network| - network.expand_outbound_requests do |outbound_requests| - outbound_requests.allow_requests_to_local_network_from_services - end - end - - Page::Main::Menu.perform(&:sign_out) + def toggle_local_requests(on) + Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: on) end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb index 3373f4f4233..6ce4217f8ac 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Create', :reliable do describe 'Merge request custom templates' do - let(:template_name) { 'custom_merge_request_template'} + let(:template_name) { 'custom_merge_request_template' } let(:template_content) { 'This is a custom merge request template test' } let(:template_project) do Resource::Project.fabricate_via_api! do |project| diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb index d73fb57a581..aa637ac4d55 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb @@ -52,7 +52,12 @@ module QA merge_request.click_commits_tab - expect(merge_request).to have_content(commit_message) + # Commit does not always display immediately and may require a page refresh + # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/368735 + # TODO: Remove page refresh logic once issue is resolved. + Support::Retrier.retry_on_exception(max_attempts: 2, reload_page: merge_request) do + expect(merge_request).to have_content(commit_message) + end end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/pages/pages_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/3_create/pages/pages_pipeline_spec.rb new file mode 100644 index 00000000000..191c4a096e7 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/pages/pages_pipeline_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Release', :runner do + # TODO: Convert back to :smoke once proved to be stable. Related issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300906 + describe 'Pages' do + let!(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'jekyll-pages-project' + project.template_name = :jekyll + end + end + + let(:pipeline) do + Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = project + pipeline.variables = + { key: :CI_PAGES_DOMAIN, value: 'nip.io', variable_type: :env_var }, + { key: :CI_PAGES_URL, value: 'http://127.0.0.1.nip.io', variable_type: :env_var } + end + end + + before do + Flow::Login.sign_in + + Resource::Runner.fabricate_via_api! do |runner| + runner.project = project + runner.executor = :docker + end + + pipeline.visit! + end + + it('runs a Pages-specific pipeline', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347669') do + Page::Project::Pipeline::Show.perform do |show| + expect(show).to have_job(:pages) + show.click_job(:pages) + end + + Page::Project::Job::Show.perform do |show| + expect(show).to have_passed(timeout: 300) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb index 8074e1fa992..3db8128bc6d 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb @@ -28,16 +28,16 @@ module QA context 'on a project with a commonly used LICENSE', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366842' do it_behaves_like 'project license detection' do - let(:license_file_name) {'bsd-3-clause'} - let(:rendered_license_name) {'BSD 3-Clause "New" or "Revised" License'} + let(:license_file_name) { 'bsd-3-clause' } + let(:rendered_license_name) { 'BSD 3-Clause "New" or "Revised" License' } end end context 'on a project with a less commonly used LICENSE', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366843' do it_behaves_like 'project license detection' do - let(:license_file_name) {'GFDL-1.2-only'} - let(:rendered_license_name) {'Other'} + let(:license_file_name) { 'GFDL-1.2-only' } + let(:rendered_license_name) { 'Other' } end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb new file mode 100644 index 00000000000..fb87ca864f4 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Repository tags', :reliable do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-for-tags' + project.initialize_with_readme = true + end + end + + let(:developer_user) do + Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) + end + + let(:maintainer_user) do + Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) + end + + let(:tag_name) { 'v0.0.1' } + let(:tag_message) { 'Version 0.0.1' } + let(:tag_release_notes) { 'Release It!' } + + shared_examples 'successful tag creation' do |user, testcase| + it "can be created by #{user}", testcase: testcase do + Flow::Login.sign_in(as: send(user)) + + create_tag_for_project(project, tag_name, tag_message, tag_release_notes) + + Page::Project::Tag::Show.perform do |show| + expect(show).to have_tag_name(tag_name) + expect(show).to have_tag_message(tag_message) + expect(show).to have_tag_release_notes(tag_release_notes) + expect(show).not_to have_element(:create_tag_button) + end + end + end + + shared_examples 'unsuccessful tag creation' do |user, testcase| + it "cannot be created by an unauthorized #{user}", testcase: testcase do + Flow::Login.sign_in(as: send(user)) + + create_tag_for_project(project, tag_name, tag_message, tag_release_notes) + + Page::Project::Tag::New.perform do |new_tag| + expect(new_tag).to have_content('You are not allowed to create this tag as it is protected.') + expect(new_tag).to have_element(:create_tag_button) + end + end + end + + context 'when not protected' do + before do + add_members_to_project(project) + end + + it_behaves_like 'successful tag creation', :developer_user, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347930' + it_behaves_like 'successful tag creation', :maintainer_user, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347929' + end + + context 'when protected' do + before do + add_members_to_project(project) + + Flow::Login.sign_in + + protect_tag_for_project(project, 'v*', 'Maintainers') + + Page::Main::Menu.perform(&:sign_out) + end + + it_behaves_like 'unsuccessful tag creation', :developer_user, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347927' + it_behaves_like 'successful tag creation', :maintainer_user, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347928' + end + + def create_tag_for_project(project, name, message, release_notes) + project.visit! + + Page::Project::Menu.perform(&:go_to_repository_tags) + Page::Project::Tag::Index.perform(&:click_new_tag_button) + + Page::Project::Tag::New.perform do |new_tag| + new_tag.fill_tag_name(name) + new_tag.fill_tag_message(message) + new_tag.fill_release_notes(release_notes) + new_tag.click_create_tag_button + end + end + + def protect_tag_for_project(project, tag, role) + project.visit! + + Page::Project::Menu.perform(&:go_to_repository_settings) + + Page::Project::Settings::Repository.perform do |setting| + setting.expand_protected_tags do |protected_tags| + protected_tags.set_tag(tag) + protected_tags.choose_access_level_role(role) + + protected_tags.click_protect_tag_button + end + end + end + + def add_members_to_project(project) + project.add_member(developer_user, Resource::Members::AccessLevel::DEVELOPER) + project.add_member(maintainer_user, Resource::Members::AccessLevel::MAINTAINER) + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb index f374ecff3f2..55df1615f5c 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb @@ -3,7 +3,8 @@ module QA RSpec.describe 'Create' do describe 'SSH keys support', :smoke do - key_title = "key for ssh tests #{Time.now.to_f}" + let(:key_title) { "key for ssh tests #{Time.now.to_f}" } + key = nil before do diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb index b4519327a62..620e6870b26 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', :smoke, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/326624', type: :investigating } do + RSpec.describe 'Create', :smoke do describe 'Personal snippet creation' do let(:snippet) do Resource::Snippet.fabricate_via_browser_ui! do |snippet| diff --git a/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb new file mode 100644 index 00000000000..f95f624d59a --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +# tagged transient due to feature-flag caching flakiness. Remove tag along with feature flag removal. +module QA + RSpec.describe 'Create', feature_flag: { name: 'source_editor_toolbar', scope: :global } do + describe 'Source editor toolbar preview' do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'empty-project-with-md' + project.initialize_with_readme = true + end + end + + let(:edited_readme_content) { 'Here is the edited content.' } + + before do + Runtime::Feature.enable(:source_editor_toolbar) + Flow::Login.sign_in + end + + after do + Runtime::Feature.disable(:source_editor_toolbar) + end + + it 'can preview markdown side-by-side while editing', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367749' do + project.visit! + Page::Project::Show.perform do |project| + project.click_file('README.md') + end + + Page::File::Show.perform(&:click_edit) + + # wait_until required due to feature_caching. Remove along with feature flag removal. + Page::File::Edit.perform do |file| + Support::Waiter.wait_until(sleep_interval: 2, max_duration: 60, reload_page: page, + retry_on_exception: true) do + expect(file).to have_element(:editor_toolbar_button) + end + file.remove_content + file.click_editor_toolbar + file.add_content('# ' + edited_readme_content) + file.wait_for_markdown_preview('h1', edited_readme_content) + file.commit_changes + end + + Page::File::Show.perform do |file| + aggregate_failures 'file details' do + expect(file).to have_notice('Your changes have been successfully committed.') + expect(file).to have_file_content(edited_readme_content) + end + end + end + end + end +end |