From 190a0fba5fb4ac3fd1416c2b97f24d64fe89c4fc Mon Sep 17 00:00:00 2001 From: Andrejs Cunskis <acunskis@gitlab.com> Date: Wed, 25 Sep 2024 06:24:56 +0000 Subject: [PATCH] Implement headless readiness check for gitlab Use web browser for sign in page check on dot_com environments Add specs for dot_com scenario --- qa/qa/ce/strategy.rb | 5 +- qa/qa/tools/readiness_check.rb | 124 ++++++++++++++++++++++++++ qa/spec/tools/readiness_check_spec.rb | 92 +++++++++++++++++++ 3 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 qa/qa/tools/readiness_check.rb create mode 100644 qa/spec/tools/readiness_check_spec.rb diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb index 93b94db840fea..a926c9dac1369 100644 --- a/qa/qa/ce/strategy.rb +++ b/qa/qa/ce/strategy.rb @@ -16,9 +16,8 @@ def perform_before_hooks return false end - QA::Support::Retrier.retry_on_exception do - QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) - end + # Perform app readiness check before continuing with the whole test suite + Tools::ReadinessCheck.perform(wait: 60) # Reset admin password if admin token is present but can't be used due to expired password reset_admin_password! diff --git a/qa/qa/tools/readiness_check.rb b/qa/qa/tools/readiness_check.rb new file mode 100644 index 0000000000000..ed6962d0f1c45 --- /dev/null +++ b/qa/qa/tools/readiness_check.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require "nokogiri" + +module QA + module Tools + # Helper to assert GitLab instance readiness without starting a web browser + # + class ReadinessCheck + include Support::API + + def self.perform(wait: 60) + new(wait: wait).perform + end + + def initialize(wait:) + @wait = wait + end + + # Validate gitlab readiness via check for presence of sign-in-form element + # + # @return [void] + def perform + error = nil + + info("Waiting for Gitlab to become ready!") + Support::Retrier.retry_until(max_duration: wait, sleep_interval: 1, raise_on_failure: false, log: false) do + result = !required_elements_missing? + error = nil + + result + rescue StandardError => e + error = "#{error_base} #{e.message}" + + false + end + raise error if error + + info("Gitlab is ready!") + end + + private + + delegate :debug, :info, to: QA::Runtime::Logger + + attr_reader :wait + + # Sign in page url + # + # @return [String] + def url + @url ||= "#{Support::GitlabAddress.address_with_port(with_default_port: false)}/users/sign_in" + end + + # Error message base + # + # @return [String] + def error_base + @error_base ||= "Gitlab readiness check failed, valid sign_in page did not appear within #{wait} seconds!" + end + + # Required elements css selectors + # + # @return [Array<String>] + def elements_css + @element_css ||= QA::Page::Main::Login.elements.select(&:required?).map(&:selector_css) + end + + # Check for missing required elements on sign-in page + # + # @return [Boolean] + def required_elements_missing? + debug("Checking for required element presence on '#{url}'") + # Do not perform headless request on .com due to cloudfare + return rendered_elements_missing? if Runtime::Env.running_on_dot_com? + + response = get(url) + + unless ok_response?(response) + msg = "Got unsucessfull response code: #{response.code}" + debug(msg) && raise(msg) + end + + unless required_elements_present?(response) + msg = "Sign in page missing required elements: '#{elements_css}'" + debug(msg) && raise(msg) + end + + debug("Required elements are present!") + false + end + + # Perform check for present elements via web browser + # + # @return [Boolean] + def rendered_elements_missing? + debug("Checking for required elements via web browser") + Runtime::Browser.visit(:gitlab, Page::Main::Login) + false + rescue StandardError => e + debug("Sign in page did not render fully") + raise(e) + end + + # Validate response code is 200 + # + # @param [RestClient::Response] response + # @return [Boolean] + def ok_response?(response) + response.code == Support::API::HTTP_STATUS_OK + end + + # Check required elements are present on sign-in page + # + # @param [RestClient::Response] response + # @return [Boolean] + def required_elements_present?(response) + doc = Nokogiri::HTML.parse(response.body) + + elements_css.all? { |sel| doc.css(sel).any? } + end + end + end +end diff --git a/qa/spec/tools/readiness_check_spec.rb b/qa/spec/tools/readiness_check_spec.rb new file mode 100644 index 0000000000000..75c7e1290f9d4 --- /dev/null +++ b/qa/spec/tools/readiness_check_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +RSpec.describe QA::Tools::ReadinessCheck do + subject(:readiness_check) { described_class.new(wait: wait) } + + let(:url) { "example.com" } + let(:wait) { 1 } + let(:msg_base) { "Gitlab readiness check failed, valid sign_in page did not appear within #{wait} seconds!" } + let(:dot_com) { false } + + let(:response) { instance_double(RestClient::Response, code: code, body: body) } + let(:code) { 200 } + let(:body) { "" } + + before do + allow(QA::Runtime::Env).to receive(:running_on_dot_com?).and_return(dot_com) + allow(QA::Support::GitlabAddress).to receive(:address_with_port).with(with_default_port: false).and_return(url) + allow(readiness_check).to receive(:get).with("#{url}/users/sign_in").and_return(response) + end + + context "with successfull response" do + let(:body) do + <<~HTML + <!DOCTYPE html> + <body data-testid="login-page"> + </body> + </html> + HTML + end + + it "validates readiness" do + expect { readiness_check.perform }.not_to raise_error + end + end + + context "with missing sign in form" do + let(:body) do + <<~HTML + <!DOCTYPE html> + </html> + HTML + end + + it "raises an error on validation" do + expect { readiness_check.perform }.to raise_error(/#{msg_base} Sign in page missing required elements/) + end + end + + context "with unsuccessfull response code" do + let(:code) { 500 } + + it "raises an error on validation" do + expect { readiness_check.perform }.to raise_error( + "#{msg_base} Got unsucessfull response code: #{code}" + ) + end + end + + context "with request timeout" do + before do + allow(readiness_check).to receive(:get).and_raise(RestClient::Exceptions::OpenTimeout) + end + + it "raises an error on validation" do + expect { readiness_check.perform }.to raise_error("#{msg_base} Timed out connecting to server") + end + end + + context "when running on dot com" do + let(:dot_com) { true } + + context "with successfull check" do + before do + allow(QA::Runtime::Browser).to receive(:visit).with(:gitlab, QA::Page::Main::Login) + end + + it "validates readiness" do + expect { readiness_check.perform }.not_to raise_error + end + end + + context "with unsuccessfull check" do + before do + allow(QA::Runtime::Browser).to receive(:visit).with(:gitlab, QA::Page::Main::Login).and_raise("not loaded") + end + + it "raises an error on validation" do + expect { readiness_check.perform }.to raise_error("#{msg_base} not loaded") + end + end + end +end -- GitLab