lib/playwright/har_router.rb



module Playwright
  class HarRouter
    # @param local_utils [LocalUtils]
    # @param file [String]
    # @param not_found_action [String] 'abort' or 'fallback'
    # @param url_match [String||Regexp|nil]
    def self.create(local_utils, file, not_found_action, url_match: nil)
      har_id = local_utils.har_open(file)

      new(
        local_utils: local_utils,
        har_id: har_id,
        not_found_action: not_found_action,
        url_match: url_match,
      )
    end

    # @param local_utils [LocalUtils]
    # @param har_id [String]
    # @param not_found_action [String] 'abort' or 'fallback'
    # @param url_match [String||Regexp|nil]
    def initialize(local_utils:, har_id:, not_found_action:,  url_match: nil)
      unless ['abort', 'fallback'].include?(not_found_action)
        raise ArgumentError.new("not_found_action must be either 'abort' or 'fallback'. '#{not_found_action}' is specified.")
      end

      @local_utils = local_utils
      @har_id = har_id
      @not_found_action = not_found_action
      @url_match = url_match || '**/*'
      @debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'
    end

    private def handle(route, request)
      response = @local_utils.har_lookup(
        har_id: @har_id,
        url: request.url,
        method: request.method,
        headers: request.headers_array,
        post_data: request.post_data_buffer,
        is_navigation_request: request.navigation_request?,
      )
      case response['action']
      when 'redirect'
        redirect_url = response['redirectURL']
        puts "pw:api HAR: #{request.url} redirected to #{redirect_url}" if @debug
        route.redirect_navigation_request(redirect_url)
      when 'fulfill'
        # If the response status is -1, the request was canceled or stalled, so we just stall it here.
        # See https://github.com/microsoft/playwright/issues/29311.
        # TODO: it'd be better to abort such requests, but then we likely need to respect the timing,
        # because the request might have been stalled for a long time until the very end of the
        # test when HAR was recorded but we'd abort it immediately.
        return if response['status'] == -1

        route.fulfill(
          status: response['status'],
          headers: response['headers'].map { |header| [header['name'], header['value']] }.to_h,
          body: Base64.strict_decode64(response['body']),
        )
      else
        # Report the error, but fall through to the default handler.
        if response['action'] == 'error'
          puts "pw:api HAR: #{response['message']} redirected to #{redirect_url}" if @debug
        end

        if @not_found_action == 'abort'
          route.abort
        else
          route.fallback
        end
      end
    end

    def add_context_route(context)
      context.route(@url_match, method(:handle))
      context.once(Events::BrowserContext::Close, method(:dispose))
    end

    def add_page_route(page)
      page.route(@url_match, method(:handle))
      page.once(Events::Page::Close, method(:dispose))
    end

    def dispose
      @local_utils.async_har_close(@har_id)
    end
  end
end