Module: Bard::AttachmentField::TestHelper

Defined in:
lib/bard/attachment_field/cucumber.rb

Class Method Summary collapse

Class Method Details

.attach_files(session, element_id, file_paths) ⇒ Object

File inputs inside shadow DOM can’t be set directly by browser automation, so we use a temp regular DOM input and transfer files via JavaScript.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/bard/attachment_field/cucumber.rb', line 38

def attach_files(session, element_id, file_paths)
  session.execute_script("document.body.insertAdjacentHTML('beforeend', '<input type=\"file\" id=\"_pw_file_helper\" multiple style=\"opacity:0;position:absolute;pointer-events:none\">')")

  temp_input = session.document.find("#_pw_file_helper", visible: :all)
  temp_input.set(file_paths)

  session.execute_script(<<~JS)
    (() => {
      const temp = document.getElementById('_pw_file_helper');
      const host = document.getElementById('#{element_id}');
      host.addFiles(temp.files);
      temp.remove();
    })()
  JS
end

.find_field(session, label) ⇒ Object



26
27
28
29
30
31
32
33
34
# File 'lib/bard/attachment_field/cucumber.rb', line 26

def find_field(session, label)
  label_element = begin
    session.find("label", exact_text: label, visible: :all, match: :first)
  rescue Capybara::ElementNotFound
    session.find("label", text: /^#{Regexp.escape(label)}/, visible: :all, match: :first)
  end
  element_id = label_element[:for]
  session.find("input-attachment##{element_id}")
end

.get_files(field) ⇒ Object



94
95
96
# File 'lib/bard/attachment_field/cucumber.rb', line 94

def get_files(field)
  field.all("attachment-file").map { |e| e[:filename] }
end

.resolve_fixture_path(filename) ⇒ Object



22
23
24
# File 'lib/bard/attachment_field/cucumber.rb', line 22

def resolve_fixture_path(filename)
  File.expand_path(filename, fixtures_path.call)
end

.validation_messages(session, element) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/bard/attachment_field/cucumber.rb', line 98

def validation_messages(session, element)
  messages = []
  messages << element.evaluate_script("this.validationMessage")
  # Get validation errors from attachment-file elements' shadow DOM
  child_errors = session.evaluate_script(<<~JS, element[:id])
    ((elementId) => {
      const host = document.getElementById(elementId);
      const files = host.querySelectorAll('attachment-file');
      return Array.from(files).map(f => {
        const errorEl = f.shadowRoot?.querySelector('.validation-error');
        return errorEl?.textContent || '';
      }).filter(e => e);
    })(arguments[0])
  JS
  messages.concat(child_errors)
  messages.select { |m| m && !m.empty? }
end

.wait_for_files(session, element_id, minimum, timeout: 15) ⇒ Object



54
55
56
57
58
59
60
61
62
63
# File 'lib/bard/attachment_field/cucumber.rb', line 54

def wait_for_files(session, element_id, minimum, timeout: 15)
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  loop do
    count = session.evaluate_script("document.getElementById(`#{element_id}`).querySelectorAll('attachment-file').length")
    break if count >= minimum
    elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
    raise "Expected at least #{minimum} attachment-file(s) in ##{element_id}, found #{count} after #{timeout}s" if elapsed > timeout
    sleep 0.1
  end
end

.wait_for_no_files(session, element_id, selector = "attachment-file", timeout: 10) ⇒ Object



65
66
67
68
69
70
71
72
73
74
# File 'lib/bard/attachment_field/cucumber.rb', line 65

def wait_for_no_files(session, element_id, selector = "attachment-file", timeout: 10)
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  loop do
    count = session.evaluate_script("document.getElementById(`#{element_id}`).querySelectorAll(`#{selector}`).length")
    break if count == 0
    elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
    raise "Expected no #{selector} in ##{element_id}, found #{count} after #{timeout}s" if elapsed > timeout
    sleep 0.1
  end
end

.wait_for_upload(session, element_id, timeout: 30) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/bard/attachment_field/cucumber.rb', line 76

def wait_for_upload(session, element_id, timeout: 30)
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  loop do
    result = session.evaluate_script(<<~JS)
      (() => {
        const files = document.getElementById('#{element_id}').querySelectorAll('attachment-file');
        return Array.from(files).map(e => ({ state: e.getAttribute('state'), value: e.value }));
      })()
    JS
    states_done = result.all? { |f| f["state"] == "complete" || f["state"] == "error" }
    values_set = result.all? { |f| f["state"] == "error" || (f["value"] && !f["value"].empty?) }
    break if states_done && values_set
    elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
    raise "Uploads not complete after #{timeout}s (files=#{result})" if elapsed > timeout
    sleep 0.1
  end
end