Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

08/07/24 form filler #214

Merged
merged 15 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/assets/builds/tailwind.css

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions app/services/applier/ashby_form_filler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module Applier
class AshbyFormFiller < FormFiller
def initialize(payload = sample_payload)
super
end

def attach_file_to_application
hidden_element.attach_file(@filepath)
end

def click_submit_button
sleep 2 # temporary -- just for testing
p "I didn't actually submit the form."
end

def handle_location
input_field = find(:css, "label[for='#{@locator}']").sibling('input')
@value.chars.each do |char|
input_field.send_keys(char)
break page.document.find('div', exact_text: @value).click if page.document.has_selector?('div', exact_text: @value)
end
sleep 0.1 # Otherwise the pop-up menu obscures the next field
end

def hidden_element
find("##{@locator}", visible: false)
end

def sample_payload
{
user_fullname: 'John Smith',
apply_url: 'https://jobs.ashbyhq.com/lightdash/9efa292a-cc34-4388-90a2-2bed5126ace4',
form_locator: '#form',
fields: [
{
locator: '_systemfield_name',
interaction: :input,
value: 'John Smith'
},
{
locator: '_systemfield_email',
interaction: :input,
value: 'j.smith@example.com'
},
{
locator: '00415714-75f3-49b9-b856-ac674fd5ce8b',
interaction: :location,
value: 'London, Greater London, England, United Kingdom'
},
{
locator: '36913b1f-c34f-4693-919c-400304a2a11d',
interaction: :input,
value: 'https://www.linkedin.com/in/my_profile'
},
{
locator: 'f93bff8c-2442-42b7-b040-3876fa160aba',
interaction: :input,
value: "This is a very good company to work for. Fantastic reviews on Glassdoor. I have long dreamed of applying to work at this company. You do amazing and innovative things!"
},
{
locator: '_systemfield_resume',
interaction: :upload,
value: File.open('public/Obretetskiy_cv.pdf')
}
]
}
end
end
end
48 changes: 48 additions & 0 deletions app/services/applier/devit_form_filler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

module Applier
class DevitFormFiller < FormFiller
def initialize(payload = sample_payload)
super
end

def submit_button
first(:button, text: /\bsend\b/i) || first(:link, text: /\bsend\b/i)
end

def sample_payload
{
user_fullname: 'John Smith',
apply_url: 'https://devitjobs.uk/jobs/Critical-Software-Software-Engineer',
form_locator: 'form',
fields: [
{
locator: 'name',
interaction: :input,
value: 'John Smith'
},
{
locator: 'email',
interaction: :input,
value: 'j.smith@example.com'
},
{
locator: 'isFromEurope',
interaction: :radiogroup,
value: 'Yes'
},
{
locator: '#cvFileId',
interaction: :upload,
value: File.open('public/Obretetskiy_cv.pdf')
},
{
locator: 'motivationLetter',
interaction: :input,
value: 'Thank you for considering my application. It really is an honor to apply to your company. Please hire me. I would like to work here very much. I promise to work very very hard and always get along well with my coworkers.'
}
]
}
end
end
end
144 changes: 103 additions & 41 deletions app/services/applier/form_filler.rb
Original file line number Diff line number Diff line change
@@ -1,69 +1,131 @@
# frozen_string_literal: true

require 'json'
# require 'open-uri'
# require 'htmltoword'

# TODO: Handle job posting becoming closed (redirect or notification on page)
module Applier
# Core class for filling out forms using Capybara
class FormFiller < ApplicationTask
include Capybara::DSL

def initialize(job_application, payload)
@job_application = job_application
@url = @job_application.job.posting_url
@ats = @job_application.job.applicant_tracking_system
@fields = JSON.parse(payload)
# @user = @job_application.application_process.user
@errors = nil

# include the relevant ATS form_filler module
include_ats_module
def initialize(payload)
@application_form = payload[:form_locator]
@fields = payload[:fields]
@session = Capybara::Session.new(:selenium)
@url = payload[:apply_url]
@user_fullname = payload[:user_fullname]
end

def call
return unless processable
return false unless processable

process
rescue StandardError => e
Rails.logger.error "Error running FormFiller: #{e.message}"
nil
using_session(@session) do
process
end
end

private

def processable
@url && @fields && @job_application && @ats
@url && @fields && @session
end

def process
p "Hello from FormFiller!"
submit_application
visit_url
fill_application_form
click_submit_button
verify_submission
ensure
@session.quit
end

def include_ats_module
ats_name = @ats.name.gsub(/\W/, '').capitalize
module_name = "Ats::#{ats_name}::SubmitApplication"
extend Object.const_get(module_name) if Object.const_defined?(module_name)
def apply_button
find(:css, 'button, a', text: /apply/i, match: :first)
end

def submit_application
return super if defined?(super)
def attach_file_to_application
find(@locator).attach_file(@filepath)
end

puts "Write a submit method for #{@ats.name}!"
return
def click_apply_button
apply_button.click
end

# def take_screenshot_and_store(session, job_application)
# screenshot_path = Rails.root.join('tmp', "screenshot-#{job_application.id}.png")
# session.save_screenshot(screenshot_path)
def click_submit_button
sleep 2 # temporary -- just for testing
submit_button.click
end

# file = File.open(screenshot_path)
# job_app = job_application
# job_app.screenshot.attach(io: file, filename: "screenshot-#{job_application.id}.png", content_type: 'image/png')
def doc_tmp_file
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one to go into more detail on later with JB on how we want to handle files - he was discussing markdown for cover letters for example

docx = Htmltoword::Document.create(@value)
@filepath = Rails.root.join("tmp", "Cover Letter_#{unique_string}.docx")
File.binwrite(@filepath, docx)
end

def fill_application_form
click_apply_button
fill_in_all_fields
end

# File.delete(screenshot_path)
# end
def fill_in_all_fields
within(@application_form) do
@fields.each { |field| fill_in_field(field) }
end
end

def fill_in_field(field)
@locator = field[:locator]
@value = field[:value]
send(:"handle_#{field[:interaction]}")
rescue Capybara::ElementNotFound => e
p e.message
end

def handle_input
fill_in(@locator, with: @value)
end

def handle_radiogroup
choose(option: @value, name: @locator)
end

def handle_multi_select
within(response_field) do
@value.each { |value| check(value) }
end
end

def handle_upload
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nicely organised code

@value.instance_of?(String) ? doc_tmp_file : pdf_tmp_file
attach_file_to_application
end

def pdf_tmp_file
@filepath = Rails.root.join("tmp", "Resume - #{unique_string}.pdf")
File.binwrite(@filepath, @value)
end

def response_field
first('label', text: @locator)
rescue Capybara::ElementNotFound
first('div', text: @locator)
end

def submit_button
first(:button, text: /submit/i) || first(:link, text: /submit/i)
end

def timestamp
Time.now.to_s.truncate(19).gsub(/\D/, '')
end

def unique_string
"#{@user_fullname}_#{timestamp}"
end

def verify_submission
sleep 4 # temporary -- just for testing
# TODO: add logic to check for successful submission message or other indicators
end

def visit_url
visit(@url)
p "Successfully reached #{@url}"
end
end
end
Loading
Loading